Compare commits
9 Commits
b40fcea2c2
...
main
Author | SHA1 | Date | |
---|---|---|---|
403618888c
|
|||
aec2f6145e
|
|||
77cc3c45de
|
|||
9282e0bbc6
|
|||
52008a0110
|
|||
b18b59eeb0
|
|||
06d6faf350
|
|||
b753cfa761
|
|||
2b59fff6a7
|
@ -36,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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -3,10 +3,4 @@
|
||||
|
||||
#cmakedefine REFCOUNT_HAS_THREADS
|
||||
|
||||
#if defined(REFCOUNT_HAS_THREADS) \
|
||||
&& (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L)
|
||||
# error \
|
||||
"RefCount needs to be compiled with at least C11 for thread support to work."
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -66,6 +66,9 @@ typedef struct RefcountContext {
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
mtx_t so_mtx; //<! Mutex protecting static_objects.
|
||||
mtx_t gr_mtx; //<! Mutex protecting gc_roots.
|
||||
_Atomic bool doing_gc;
|
||||
#else
|
||||
bool doing_gc;
|
||||
#endif
|
||||
} RefcountContext;
|
||||
|
||||
@ -159,7 +162,20 @@ static inline bool refcount_context_is_static(const RefcountContext *ctx,
|
||||
*/
|
||||
static inline uint64_t refcount_context_num_refs(const RefcountContext *ctx,
|
||||
void *obj) {
|
||||
return REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count;
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
if (!mtx_lock(&REFCOUNT_OBJECT_ENTRY(ctx, obj)->weak_ref->mtx)) {
|
||||
// just try our best to estimate, if we are on a platform where 64bit
|
||||
// stores are not atomic (or this is not aligned properly), this might
|
||||
// race and give a bogus number
|
||||
return REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count;
|
||||
}
|
||||
#endif
|
||||
const uint64_t count =
|
||||
REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count;
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
mtx_unlock(&REFCOUNT_OBJECT_ENTRY(ctx, obj)->weak_ref->mtx);
|
||||
#endif
|
||||
return count;
|
||||
}
|
||||
|
||||
bool refcount_context_init_obj(const RefcountContext *ctx, void *obj);
|
||||
@ -192,10 +208,23 @@ 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);
|
||||
|
||||
/**
|
||||
* Test weather a give context is currently performing a GC operation.
|
||||
* @param ctx The #RefcountContext
|
||||
* @return True if the context is currently doing a GC operation, false
|
||||
* otherwise
|
||||
*/
|
||||
static bool refcount_context_is_doing_gc(const RefcountContext *ctx) {
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
return atomic_load_explicit(&ctx->doing_gc, memory_order_relaxed);
|
||||
#else
|
||||
return ctx->doing_gc;
|
||||
#endif
|
||||
}
|
||||
|
||||
RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx,
|
||||
void *obj);
|
||||
|
||||
@ -321,16 +350,7 @@ static inline void refcount_unref_as_callback(void *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
|
||||
* Same as #refcount_context_garbage_collect, but only operates on the global
|
||||
* context.
|
||||
* @return The number of object's freed, or -1 if an error occurred
|
||||
*/
|
||||
@ -338,6 +358,16 @@ static inline ptrdiff_t refcount_garbage_collect(void) {
|
||||
return refcount_context_garbage_collect(refcount_default_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as #refcount_context_is_doing_gc, but only operates on the global
|
||||
* context.
|
||||
* @return True if the context is currently doing a GC operation, false
|
||||
* otherwise
|
||||
*/
|
||||
static inline bool refcount_is_doing_gc(void) {
|
||||
return refcount_context_is_doing_gc(refcount_default_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as #refcount_context_make_weakref, but operates on the global context.
|
||||
* @param obj The object to reference
|
||||
@ -424,6 +454,24 @@ static inline bool refcount_remove_destructor(void *obj, void *key) {
|
||||
key);
|
||||
}
|
||||
|
||||
// Debug Functions
|
||||
|
||||
uint64_t refcount_debug_context_count_object(const RefcountContext *ctx,
|
||||
void *obj, void *target);
|
||||
|
||||
/**
|
||||
* Same as #refcount_debug_context_count_object, but only operates on the global
|
||||
* context.
|
||||
* @param obj The root object
|
||||
* @param target The object to look for
|
||||
* @return The number of times the target appeared in the reference tree of the
|
||||
* root
|
||||
*/
|
||||
static inline uint64_t refcount_debug_count_object(void *obj, void *target) {
|
||||
return refcount_debug_context_count_object(refcount_default_context, obj,
|
||||
target);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
156
src/refcount.c
156
src/refcount.c
@ -26,19 +26,24 @@
|
||||
(atomic_fetch_sub_explicit(obj, 1, memory_order_relaxed))
|
||||
# define lock_entry_mtx(obj) (lock_mutex(&obj->weak_ref->mtx))
|
||||
# define unlock_entry_mtx(obj) (unlock_mutex(&obj->weak_ref->mtx))
|
||||
# define store_flag_maybe_atomic(obj, value) \
|
||||
(atomic_store_explicit(obj, value, memory_order_relaxed))
|
||||
#else
|
||||
# define lock_mutex(m) true
|
||||
# define unlock_mutex(m) true
|
||||
# define store_wr_count(obj, c) (*(obj) = (c))
|
||||
# define inc_wr_count(obj) ((*(obj))++)
|
||||
# define dec_wr_count(obj) ((*(obj))--)
|
||||
# define lock_entry_mtx(obj) true
|
||||
# define unlock_entry_mtx(obj) true
|
||||
# define lock_mutex(m) true
|
||||
# define unlock_mutex(m) true
|
||||
# define store_wr_count(obj, c) (*(obj) = (c))
|
||||
# define inc_wr_count(obj) ((*(obj))++)
|
||||
# define dec_wr_count(obj) ((*(obj))--)
|
||||
# define lock_entry_mtx(obj) true
|
||||
# define unlock_entry_mtx(obj) true
|
||||
# define store_flag_maybe_atomic(obj, value) (*(obj) = (value))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Default context to use for functions that do not take a context. You must
|
||||
* initialize this before use.
|
||||
*
|
||||
* > The default value of this is NULL.
|
||||
*/
|
||||
RefcountContext *refcount_default_context = NULL;
|
||||
|
||||
@ -85,27 +90,37 @@ refcount_make_context(size_t entry_offset,
|
||||
|
||||
ctx->static_objects = NULL;
|
||||
ctx->gc_roots = NULL;
|
||||
ctx->doing_gc = false;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a #RefcountContext and free any associated resources. This also
|
||||
* causes all remaining references, both static and non-static, to be dropped
|
||||
* and the context's destroy callback to be called on each object.
|
||||
* Callback that deinits a static, but sets its static_entry field to NULL
|
||||
* first. Used in #refcount_context_destroy.
|
||||
*/
|
||||
static void deinit_static_for_context_destroy(void *obj, void *ctx_raw) {
|
||||
RefcountContext *ctx = ctx_raw;
|
||||
ENTRY->impl.static_entry = NULL;
|
||||
refcount_context_deinit_static(ctx, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a #RefcountContext and free any associated resources. This first
|
||||
* frees all static objects, then runs the garbage collector.
|
||||
* @param ctx The #RefcountContext
|
||||
*/
|
||||
void refcount_context_destroy(RefcountContext *ctx) {
|
||||
refcount_list_free_with_data_full(
|
||||
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx,
|
||||
&ctx->alloc);
|
||||
refcount_list_free_with_data_full(ctx->static_objects,
|
||||
deinit_static_for_context_destroy, ctx,
|
||||
&ctx->alloc);
|
||||
|
||||
refcount_context_garbage_collect(ctx);
|
||||
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
mtx_destroy(&ctx->so_mtx);
|
||||
mtx_destroy(&ctx->gr_mtx);
|
||||
#endif
|
||||
|
||||
refcount_context_garbage_collect(ctx);
|
||||
|
||||
refcount_free(&ctx->alloc, ctx);
|
||||
}
|
||||
|
||||
@ -170,7 +185,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 +199,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;
|
||||
}
|
||||
@ -233,7 +250,7 @@ end:
|
||||
* @param refs Where to store the refs
|
||||
* @return True on success
|
||||
*/
|
||||
static inline bool obj_held_refs(RefcountContext *ctx, void *obj,
|
||||
static inline bool obj_held_refs(const RefcountContext *ctx, void *obj,
|
||||
RefcountList **refs) {
|
||||
if (ctx->held_refs_callback) {
|
||||
return ctx->held_refs_callback(obj, refs, ctx->user_data);
|
||||
@ -317,8 +334,11 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
||||
ht_free(ENTRY->destructors);
|
||||
unref_weakref(ctx, ENTRY->weak_ref);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
ctx->static_objects = refcount_list_remove_full(
|
||||
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
||||
// this is set to null if we are destroying the context
|
||||
if (ENTRY->impl.static_entry) {
|
||||
ctx->static_objects = refcount_list_remove_full(
|
||||
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
||||
}
|
||||
refcount_list_free_with_data_full(
|
||||
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
||||
success = true;
|
||||
@ -394,19 +414,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 +452,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 +462,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 +492,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 +526,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.
|
||||
*/
|
||||
@ -668,8 +653,8 @@ static ptrdiff_t check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) {
|
||||
// future)
|
||||
while (queue) {
|
||||
void *obj = queue->data;
|
||||
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||
if (!obj || refcount_context_is_static(ctx, obj)) {
|
||||
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||
continue;
|
||||
}
|
||||
uintptr_t count;
|
||||
@ -678,14 +663,14 @@ static ptrdiff_t check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) {
|
||||
} else {
|
||||
count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count;
|
||||
++seen_objects;
|
||||
// don't recuse into objects multiple times
|
||||
obj_held_refs(ctx, obj, &queue);
|
||||
}
|
||||
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||
if (count > 0) {
|
||||
ht_insert(counts, obj, HT_STUFF(--count));
|
||||
if (count == 0) {
|
||||
++clear_objects;
|
||||
}
|
||||
obj_held_refs(ctx, obj, &queue);
|
||||
}
|
||||
}
|
||||
ptrdiff_t freed_count = 0;
|
||||
@ -722,6 +707,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||
if (!lock_mutex(&ctx->gr_mtx)) {
|
||||
return -1;
|
||||
}
|
||||
store_flag_maybe_atomic(&ctx->doing_gc, true);
|
||||
ptrdiff_t total_cleared = 0;
|
||||
RefcountList *root = ctx->gc_roots;
|
||||
while (root) {
|
||||
@ -731,6 +717,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||
}
|
||||
total_cleared += res;
|
||||
}
|
||||
store_flag_maybe_atomic(&ctx->doing_gc, false);
|
||||
unlock_mutex(&ctx->gr_mtx);
|
||||
return total_cleared;
|
||||
}
|
||||
@ -856,3 +843,48 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
|
||||
unlock_entry_mtx(ENTRY);
|
||||
return success;
|
||||
}
|
||||
|
||||
// Debug Functions
|
||||
|
||||
/**
|
||||
* Count all instances of a target object by walking the references of some root
|
||||
* object. This is for debug purposes only. The root is not included in the
|
||||
* count (as in, if `obj == target`, it will not be counted).
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The root object
|
||||
* @param target The object to look for
|
||||
* @return The number of times the target appeared in the reference tree of the
|
||||
* root
|
||||
*/
|
||||
uint64_t refcount_debug_context_count_object(const RefcountContext *ctx,
|
||||
void *obj, void *target) {
|
||||
static const HTTableFunctions SEEN_FNS = {
|
||||
.destroy_key = NULL,
|
||||
.destroy_value = NULL,
|
||||
.equal = ht_intptr_equal_callback,
|
||||
.hash = ht_intptr_hash_callback,
|
||||
.user_data = NULL,
|
||||
};
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
RefcountList *queue = NULL;
|
||||
obj_held_refs(ctx, obj, &queue);
|
||||
uint64_t total_count = 0;
|
||||
HTTable *seen = ht_new(&SEEN_FNS, &ctx->ht_alloc, NULL);
|
||||
while (queue) {
|
||||
void *cur = queue->data;
|
||||
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||
// count NULL
|
||||
if (cur == target) {
|
||||
++total_count;
|
||||
}
|
||||
// but don't try to descend into it
|
||||
if (cur && !ht_has(seen, cur)) {
|
||||
ht_insert(seen, cur, NULL);
|
||||
obj_held_refs(ctx, cur, &queue);
|
||||
}
|
||||
}
|
||||
ht_free(seen);
|
||||
return total_count;
|
||||
}
|
||||
|
@ -26,7 +26,16 @@ bool held_refs_callback(void *a_raw, RefcountList **out, void *ignored) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy_callback(void *a_raw, void *ignored) {
|
||||
struct ContextAndFlag {
|
||||
const RefcountContext *ctx;
|
||||
bool should_be_doing_gc;
|
||||
};
|
||||
|
||||
void destroy_callback(void *a_raw, void *ctx_and_flag_raw) {
|
||||
struct ContextAndFlag *ctx_and_flag = ctx_and_flag_raw;
|
||||
if (ctx_and_flag->should_be_doing_gc) {
|
||||
assert(refcount_context_is_doing_gc(ctx_and_flag->ctx));
|
||||
}
|
||||
A *a = a_raw;
|
||||
counting_free(a->str);
|
||||
counting_free(a);
|
||||
@ -38,61 +47,70 @@ void reref_destructor(void *a, void *ctx_raw) {
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
RefcountContext *c =
|
||||
refcount_make_context(offsetof(A, refcount), held_refs_callback,
|
||||
destroy_callback, NULL, &COUNTING_ALLOCATOR);
|
||||
struct ContextAndFlag ctx_and_flag = {.should_be_doing_gc = false};
|
||||
|
||||
A static_a = {
|
||||
.num = 0,
|
||||
.str = counting_strdup("static"),
|
||||
.next = make_a(c, 0, "in static"),
|
||||
};
|
||||
refcount_context_init_static(c, &static_a);
|
||||
RefcountContext *c = refcount_make_context(
|
||||
offsetof(A, refcount), held_refs_callback, destroy_callback,
|
||||
&ctx_and_flag, &COUNTING_ALLOCATOR);
|
||||
|
||||
ctx_and_flag.ctx = c;
|
||||
|
||||
#define STATIC_A(id) \
|
||||
A static_a_##id = { \
|
||||
.num = __LINE__, \
|
||||
.str = counting_strdup("static " #id), \
|
||||
.next = make_a(c, 0, "in static " #id), \
|
||||
}; \
|
||||
refcount_context_init_static(c, &static_a_##id)
|
||||
STATIC_A(1);
|
||||
STATIC_A(2);
|
||||
STATIC_A(3);
|
||||
assert(refcount_debug_context_count_object(c, &static_a_1, static_a_1.next)
|
||||
== 1);
|
||||
assert(refcount_debug_context_count_object(c, &static_a_1, static_a_2.next)
|
||||
== 0);
|
||||
|
||||
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,13 +121,14 @@ 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);
|
||||
assert(refcount_debug_context_count_object(c, a, a) == 1);
|
||||
|
||||
refcount_context_ref(c, a);
|
||||
refcount_context_unref(c, a);
|
||||
@ -121,29 +140,33 @@ 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);
|
||||
assert(refcount_context_num_refs(c, a_with_destructor) == 1);
|
||||
assert(refcount_context_remove_destructor(c, a_with_destructor, &key));
|
||||
assert(refcount_context_ref(c, a_with_destructor));
|
||||
assert(refcount_context_garbage_collect(c) == 0);
|
||||
assert(refcount_context_unref(c, a_with_destructor));
|
||||
assert(!refcount_context_is_doing_gc(c));
|
||||
ctx_and_flag.should_be_doing_gc = true;
|
||||
assert(refcount_context_garbage_collect(c) == 26);
|
||||
ctx_and_flag.should_be_doing_gc = false;
|
||||
assert(!refcount_context_is_doing_gc(c));
|
||||
|
||||
a = make_a(c, 0, "a");
|
||||
|
||||
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,19 +190,21 @@ 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));
|
||||
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_deinit_static(c, &static_a_1);
|
||||
counting_free(static_a_1.str);
|
||||
|
||||
refcount_context_destroy(c);
|
||||
counting_free(static_a_2.str);
|
||||
counting_free(static_a_3.str);
|
||||
|
||||
check_allocator_status();
|
||||
return 0;
|
||||
|
Reference in New Issue
Block a user