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 #ifdef REFCOUNT_HAS_THREADS
mtx_t so_mtx; //<! Mutex protecting static_objects. mtx_t so_mtx; //<! Mutex protecting static_objects.
mtx_t gr_mtx; //<! Mutex protecting gc_roots. mtx_t gr_mtx; //<! Mutex protecting gc_roots.
_Atomic bool doing_gc;
#else
bool doing_gc;
#endif #endif
} RefcountContext; } 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); 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, RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx,
void *obj); 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. * context.
* @return The number of object's freed, or -1 if an error occurred * @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); 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. * Same as #refcount_context_make_weakref, but operates on the global context.
* @param obj The object to reference * @param obj The object to reference

View File

@ -26,19 +26,24 @@
(atomic_fetch_sub_explicit(obj, 1, memory_order_relaxed)) (atomic_fetch_sub_explicit(obj, 1, memory_order_relaxed))
# define lock_entry_mtx(obj) (lock_mutex(&obj->weak_ref->mtx)) # define lock_entry_mtx(obj) (lock_mutex(&obj->weak_ref->mtx))
# define unlock_entry_mtx(obj) (unlock_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 #else
# define lock_mutex(m) true # define lock_mutex(m) true
# define unlock_mutex(m) true # define unlock_mutex(m) true
# define store_wr_count(obj, c) (*(obj) = (c)) # define store_wr_count(obj, c) (*(obj) = (c))
# define inc_wr_count(obj) ((*(obj))++) # define inc_wr_count(obj) ((*(obj))++)
# define dec_wr_count(obj) ((*(obj))--) # define dec_wr_count(obj) ((*(obj))--)
# define lock_entry_mtx(obj) true # define lock_entry_mtx(obj) true
# define unlock_entry_mtx(obj) true # define unlock_entry_mtx(obj) true
# define store_flag_maybe_atomic(obj, value) (*(obj) = (value))
#endif #endif
/** /**
* Default context to use for functions that do not take a context. You must * Default context to use for functions that do not take a context. You must
* initialize this before use. * initialize this before use.
*
* > The default value of this is NULL.
*/ */
RefcountContext *refcount_default_context = NULL; RefcountContext *refcount_default_context = NULL;
@ -85,6 +90,7 @@ refcount_make_context(size_t entry_offset,
ctx->static_objects = NULL; ctx->static_objects = NULL;
ctx->gc_roots = NULL; ctx->gc_roots = NULL;
ctx->doing_gc = false;
return ctx; return ctx;
} }
@ -689,6 +695,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
if (!lock_mutex(&ctx->gr_mtx)) { if (!lock_mutex(&ctx->gr_mtx)) {
return -1; return -1;
} }
store_flag_maybe_atomic(&ctx->doing_gc, true);
ptrdiff_t total_cleared = 0; ptrdiff_t total_cleared = 0;
RefcountList *root = ctx->gc_roots; RefcountList *root = ctx->gc_roots;
while (root) { while (root) {
@ -698,6 +705,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
} }
total_cleared += res; total_cleared += res;
} }
store_flag_maybe_atomic(&ctx->doing_gc, false);
unlock_mutex(&ctx->gr_mtx); unlock_mutex(&ctx->gr_mtx);
return total_cleared; return total_cleared;
} }

View File

@ -26,7 +26,16 @@ bool held_refs_callback(void *a_raw, RefcountList **out, void *ignored) {
return true; 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; A *a = a_raw;
counting_free(a->str); counting_free(a->str);
counting_free(a); counting_free(a);
@ -38,9 +47,13 @@ void reref_destructor(void *a, void *ctx_raw) {
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
RefcountContext *c = struct ContextAndFlag ctx_and_flag = {.should_be_doing_gc = false};
refcount_make_context(offsetof(A, refcount), held_refs_callback,
destroy_callback, NULL, &COUNTING_ALLOCATOR); 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 = { A static_a = {
.num = 0, .num = 0,
@ -127,7 +140,11 @@ int main(int argc, const char **argv) {
assert(refcount_context_garbage_collect(c) == 0); assert(refcount_context_garbage_collect(c) == 0);
assert(refcount_context_num_refs(c, a_with_destructor) == 1); assert(refcount_context_num_refs(c, a_with_destructor) == 1);
assert(refcount_context_remove_destructor(c, a_with_destructor, &key)); 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); 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"); a = make_a(c, 0, "a");