Add flag to check if currently doing GC

This commit is contained in:
2025-09-08 15:55:18 -07:00
parent 06d6faf350
commit b18b59eeb0
3 changed files with 64 additions and 12 deletions

View File

@ -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;
@ -195,6 +198,20 @@ static inline void refcount_context_unref_as_callback(void *obj, void *ctx) {
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);
@ -320,7 +337,7 @@ static inline void refcount_unref_as_callback(void *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
*/
@ -328,6 +345,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

View File

@ -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,6 +90,7 @@ refcount_make_context(size_t entry_offset,
ctx->static_objects = NULL;
ctx->gc_roots = NULL;
ctx->doing_gc = false;
return ctx;
}
@ -689,6 +695,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) {
@ -698,6 +705,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;
}

View File

@ -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,9 +47,13 @@ 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};
RefcountContext *c = refcount_make_context(
offsetof(A, refcount), held_refs_callback, destroy_callback,
&ctx_and_flag, &COUNTING_ALLOCATOR);
ctx_and_flag.ctx = c;
A static_a = {
.num = 0,
@ -127,7 +140,11 @@ int main(int argc, const char **argv) {
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_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");