Add destructors
This commit is contained in:
		| @ -4,6 +4,12 @@ set(REFCOUNT_USE_THREADS | |||||||
|     ON |     ON | ||||||
|     CACHE BOOL "Weather or not RefCount should be thread aware.") |     CACHE BOOL "Weather or not RefCount should be thread aware.") | ||||||
|  |  | ||||||
|  | if(REFCOUNT_USE_THREADS) | ||||||
|  |   message("Building with thread support, setting CMAKE_C_STANDARD to 11.") | ||||||
|  | else() | ||||||
|  |   message("Building without thread support, setting CMAKE_C_STANDARD to 99.") | ||||||
|  | endif() | ||||||
|  |  | ||||||
| if(REFCOUNT_USE_THREADS) | if(REFCOUNT_USE_THREADS) | ||||||
|   set(CMAKE_C_STANDARD 11) |   set(CMAKE_C_STANDARD 11) | ||||||
| else() | else() | ||||||
| @ -22,7 +28,7 @@ include(FetchContent) | |||||||
| FetchContent_Declare( | FetchContent_Declare( | ||||||
|   ht |   ht | ||||||
|   GIT_REPOSITORY https://git.zander.im/Zander671/ht.git |   GIT_REPOSITORY https://git.zander.im/Zander671/ht.git | ||||||
|   GIT_TAG 9a1b271cfdc8ab203f9d6aa6a83cc4523de422be) |   GIT_TAG c6bdb38bb77d45f9d5083706723c84c37db56c9c) | ||||||
|  |  | ||||||
| FetchContent_MakeAvailable(ht) | FetchContent_MakeAvailable(ht) | ||||||
|  |  | ||||||
| @ -30,8 +36,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) | |||||||
|   include(CTest) |   include(CTest) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| # add_compile_options(-fsanitize=address,leak,undefined) | add_compile_options(-fsanitize=address,leak,undefined) | ||||||
| # add_link_options(-fsanitize=address,leak,undefined) | add_link_options(-fsanitize=address,leak,undefined) | ||||||
|  |  | ||||||
| configure_file(include/refcount/config.h.in include/refcount/config.h @ONLY) | configure_file(include/refcount/config.h.in include/refcount/config.h @ONLY) | ||||||
|  |  | ||||||
|  | |||||||
| @ -80,8 +80,8 @@ refcount_make_context(size_t entry_offset, | |||||||
| void refcount_context_destroy(RefcountContext *ctx); | void refcount_context_destroy(RefcountContext *ctx); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Structure holding the required information for a weak reference. That is, a |  * Opaque structure holding the required information for a weak reference. That | ||||||
|  * reference that does not prevent an object from being de-allocated. |  * is, a reference that does not prevent an object from being de-allocated. | ||||||
|  */ |  */ | ||||||
| typedef struct RefcountWeakref { | typedef struct RefcountWeakref { | ||||||
| #ifdef REFCOUNT_HAS_THREADS | #ifdef REFCOUNT_HAS_THREADS | ||||||
| @ -94,8 +94,18 @@ typedef struct RefcountWeakref { | |||||||
| } RefcountWeakref; | } RefcountWeakref; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Opaque struct holding reference data for an object. This should be included |  * Destructor callback type. Called before and object is freed after having its | ||||||
|  * as a non-pointer member of the struct you are trying to track, e.g. |  * reference count drop to zero. This function can prevent the given object | ||||||
|  |  * from being freed by increasing its reference count. | ||||||
|  |  * @param obj The object being destroyed | ||||||
|  |  * @param user_data Arbitrary user_provided data | ||||||
|  |  */ | ||||||
|  | typedef void (*refcount_destructor_callback_t)(void *obj, void *user_data); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Opaque struct holding reference data for an object. This should be | ||||||
|  |  * included as a non-pointer member of the struct you are trying to track, | ||||||
|  |  * e.g. | ||||||
|  * @code |  * @code | ||||||
|  * struct A { |  * struct A { | ||||||
|  *     int my_num; |  *     int my_num; | ||||||
| @ -106,7 +116,8 @@ typedef struct RefcountWeakref { | |||||||
|  */ |  */ | ||||||
| typedef struct RefcountEntry { | typedef struct RefcountEntry { | ||||||
|     bool is_static; //!< Whether the object is static. |     bool is_static; //!< Whether the object is static. | ||||||
|     RefcountWeakref *weak_ref; //<! Weakref for this entry. |     RefcountWeakref *weak_ref; //!< Weakref for this entry. | ||||||
|  |     HTTable *destructors; //!< Hash table of object destructors. | ||||||
|     union { |     union { | ||||||
|         struct { |         struct { | ||||||
|             uint64_t ref_count; //!< The object's reference count. |             uint64_t ref_count; //!< The object's reference count. | ||||||
| @ -226,6 +237,14 @@ static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx, | |||||||
|     return wr; |     return wr; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool refcount_context_add_destructor(const RefcountContext *ctx, void *obj, | ||||||
|  |                                      void *key, | ||||||
|  |                                      refcount_destructor_callback_t callback, | ||||||
|  |                                      void *user_data); | ||||||
|  |  | ||||||
|  | bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj, | ||||||
|  |                                         void *key); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Same as #refcount_context_is_static, but only operates on the global |  * Same as #refcount_context_is_static, but only operates on the global | ||||||
|  * context. |  * context. | ||||||
| @ -367,7 +386,7 @@ static inline void *refcount_strengthen(RefcountWeakref *wr) { | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Same as #refcount_context_weaken, but operates ojn the global context. |  * Same as #refcount_context_weaken, but operates on the global context. | ||||||
|  * @param obj The object to create a weak reference for then unref |  * @param obj The object to create a weak reference for then unref | ||||||
|  * @return The newly created weak reference, or NULL if an allocation error |  * @return The newly created weak reference, or NULL if an allocation error | ||||||
|  * occurred. In this case the object is not unrefed. |  * occurred. In this case the object is not unrefed. | ||||||
| @ -376,6 +395,35 @@ static inline RefcountWeakref *refcount_weaken(void *obj) { | |||||||
|     return refcount_context_weaken(refcount_default_context, obj); |     return refcount_context_weaken(refcount_default_context, obj); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Same as #refcount_context_add_destructor, but operates on the global context. | ||||||
|  |  * @param obj The object onto which to register the destructor | ||||||
|  |  * @param key An arbitrary value that can be later used to unregister the | ||||||
|  |  * destructor | ||||||
|  |  * @param callback The destructor itself | ||||||
|  |  * @param user_data Extra data to pass to the destructor | ||||||
|  |  * @return True on success, false on failure. On failure, nothing is registered. | ||||||
|  |  */ | ||||||
|  | static inline bool | ||||||
|  | refcount_add_destructor(void *obj, void *key, | ||||||
|  |                         refcount_destructor_callback_t callback, | ||||||
|  |                         void *user_data) { | ||||||
|  |     return refcount_context_add_destructor(refcount_default_context, obj, key, | ||||||
|  |                                            callback, user_data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Same as #refcount_context_remove_destructor, but operates on the global | ||||||
|  |  * context. | ||||||
|  |  * @param obj The object for which to unregister the destructor | ||||||
|  |  * @param key The destructors key | ||||||
|  |  * @return True on success, false on error. On error, nothing is unregistered. | ||||||
|  |  */ | ||||||
|  | static inline bool refcount_remove_destructor(void *obj, void *key) { | ||||||
|  |     return refcount_context_remove_destructor(refcount_default_context, obj, | ||||||
|  |                                               key); | ||||||
|  | } | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										144
									
								
								src/refcount.c
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								src/refcount.c
									
									
									
									
									
								
							| @ -131,6 +131,44 @@ static bool init_obj_weakref(const RefcountContext *ctx, void *obj) { | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @struct DestructorEntry | ||||||
|  |  * Hash table entry used to hold descructor callback information. | ||||||
|  |  */ | ||||||
|  | struct DestructorEntry { | ||||||
|  |     refcount_destructor_callback_t callback; //!< The callback itself. | ||||||
|  |     void *user_data; //!< User specified data to pass to the callback. | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Free a #DestructorEntry using the given context. | ||||||
|  |  * @param entry The entry to free | ||||||
|  |  * @param ctx_raw The #RefcountContext | ||||||
|  |  */ | ||||||
|  | static void free_destructor_entry_callback(void *entry, void *ctx_raw) { | ||||||
|  |     const RefcountContext *ctx = ctx_raw; | ||||||
|  |     refcount_free(&ctx->alloc, entry); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Initialize the destructor table for an object. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The object to initialize | ||||||
|  |  * @return True on success | ||||||
|  |  */ | ||||||
|  | static bool init_obj_destructor_table(const RefcountContext *ctx, void *obj) { | ||||||
|  |     ENTRY->destructors = ht_new( | ||||||
|  |         &(HTTableFunctions) { | ||||||
|  |             .equal = ht_intptr_equal_callback, | ||||||
|  |             .hash = ht_intptr_hash_callback, | ||||||
|  |             .destroy_key = NULL, | ||||||
|  |             .destroy_value = free_destructor_entry_callback, | ||||||
|  |             .user_data = (void *) ctx, | ||||||
|  |         }, | ||||||
|  |         &ctx->ht_alloc, NULL); | ||||||
|  |     return ENTRY->destructors; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Initialize the #RefcountEntry member of an object. |  * Initialize the #RefcountEntry member of an object. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
| @ -145,7 +183,13 @@ bool refcount_context_init_obj(const RefcountContext *ctx, void *obj) { | |||||||
|         ENTRY->is_static = false; |         ENTRY->is_static = false; | ||||||
|         ENTRY->impl.counted.gc_root = NULL; |         ENTRY->impl.counted.gc_root = NULL; | ||||||
|         ENTRY->impl.counted.ref_count = 0; |         ENTRY->impl.counted.ref_count = 0; | ||||||
|         init_obj_weakref(ctx, obj); |         if (!init_obj_destructor_table(ctx, obj)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if (!init_obj_weakref(ctx, obj)) { | ||||||
|  |             ht_free(ENTRY->destructors); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| @ -162,7 +206,11 @@ bool refcount_context_init_static(RefcountContext *ctx, void *obj) { | |||||||
|     } |     } | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     ENTRY->is_static = true; |     ENTRY->is_static = true; | ||||||
|  |     if (!init_obj_destructor_table(ctx, obj)) { | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|     if (!init_obj_weakref(ctx, obj)) { |     if (!init_obj_weakref(ctx, obj)) { | ||||||
|  |         refcount_free(&ctx->alloc, ENTRY->destructors); | ||||||
|         goto end; |         goto end; | ||||||
|     } |     } | ||||||
|     RefcountList *new_static_objects = |     RefcountList *new_static_objects = | ||||||
| @ -208,6 +256,40 @@ static void unref_weakref(const RefcountContext *ctx, RefcountWeakref *wr) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Used to pass two values to #call_object_destructors_foreach_callback. | ||||||
|  |  */ | ||||||
|  | struct ContextAndObject { | ||||||
|  |     const RefcountContext *ctx; //!< The #RefcountContext. | ||||||
|  |     void *obj; //!< The object to check. | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Callback used from #call_object_destructors. | ||||||
|  |  * @param key The hash table key | ||||||
|  |  * @param entry_raw The #DestructorEntry | ||||||
|  |  * @param ctx_and_obj_raw The #ContextAndObject | ||||||
|  |  * @return True if the object's refcount is non-zero, false otherwise | ||||||
|  |  */ | ||||||
|  | static bool call_object_destructors_foreach_callback(void *key, void *entry_raw, | ||||||
|  |                                                      void *ctx_and_obj_raw) { | ||||||
|  |     struct ContextAndObject *ctx_and_obj = ctx_and_obj_raw; | ||||||
|  |     const struct DestructorEntry *entry = entry_raw; | ||||||
|  |     entry->callback(ctx_and_obj->obj, entry->user_data); | ||||||
|  |     // if the refcount has increased past 0, stop looping | ||||||
|  |     return refcount_context_num_refs(ctx_and_obj->ctx, ctx_and_obj->obj); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Call descructors for an object. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The object to call descructors for | ||||||
|  |  */ | ||||||
|  | static void call_object_destructors(const RefcountContext *ctx, void *obj) { | ||||||
|  |     ht_foreach(ENTRY->destructors, call_object_destructors_foreach_callback, | ||||||
|  |                &(struct ContextAndObject) {.ctx = ctx, .obj = obj}); | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Unregister a static object in a context. |  * Unregister a static object in a context. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
| @ -231,6 +313,8 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { | |||||||
|         goto end; |         goto end; | ||||||
|     } |     } | ||||||
|     ENTRY->weak_ref->data = NULL; |     ENTRY->weak_ref->data = NULL; | ||||||
|  |     call_object_destructors(ctx, obj); | ||||||
|  |     ht_free(ENTRY->destructors); | ||||||
|     unref_weakref(ctx, ENTRY->weak_ref); |     unref_weakref(ctx, ENTRY->weak_ref); | ||||||
|     unlock_entry_mtx(ENTRY); |     unlock_entry_mtx(ENTRY); | ||||||
|     ctx->static_objects = refcount_list_remove_full( |     ctx->static_objects = refcount_list_remove_full( | ||||||
| @ -330,7 +414,13 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj, | |||||||
|         return obj; |         return obj; | ||||||
|     } |     } | ||||||
|     if (ENTRY->impl.counted.ref_count <= 1) { |     if (ENTRY->impl.counted.ref_count <= 1) { | ||||||
|         *queue = refcount_list_push_full(*queue, obj, &ctx->alloc); |         ENTRY->impl.counted.ref_count = 0; | ||||||
|  |         call_object_destructors(ctx, obj); | ||||||
|  |         if (!ENTRY->impl.counted.ref_count) { | ||||||
|  |             // if we still have no refs after calling destructors, it's really | ||||||
|  |             // time to free this object | ||||||
|  |             *queue = refcount_list_push_full(*queue, obj, &ctx->alloc); | ||||||
|  |         } | ||||||
|         unlock_entry_mtx(ENTRY); |         unlock_entry_mtx(ENTRY); | ||||||
|         return NULL; |         return NULL; | ||||||
|     } else { |     } else { | ||||||
| @ -373,6 +463,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) { | |||||||
|     } |     } | ||||||
|     remove_gc_root(ctx, obj); |     remove_gc_root(ctx, obj); | ||||||
|     ENTRY->weak_ref->data = NULL; |     ENTRY->weak_ref->data = NULL; | ||||||
|  |     ht_free(ENTRY->destructors); | ||||||
|     unlock_entry_mtx(ENTRY); |     unlock_entry_mtx(ENTRY); | ||||||
|     unref_weakref(ctx, ENTRY->weak_ref); |     unref_weakref(ctx, ENTRY->weak_ref); | ||||||
|     if (ctx->destroy_callback) { |     if (ctx->destroy_callback) { | ||||||
| @ -381,7 +472,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) { | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Continually release held references and object held in a queue. |  * Continually release held references and objects held in a queue. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
|  * @param queue The queue |  * @param queue The queue | ||||||
|  * @param toplevel Toplevel object that triggered the unref |  * @param toplevel Toplevel object that triggered the unref | ||||||
| @ -646,3 +737,50 @@ void *refcount_context_ref_weakref(const RefcountContext *ctx, | |||||||
|     unlock_mutex(&wr->mtx); |     unlock_mutex(&wr->mtx); | ||||||
|     return obj; |     return obj; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Register a destructor to be called right before an object is freed. If the | ||||||
|  |  * destructor adds a new reference to the object, the object is not freed. The | ||||||
|  |  * destructor will then be run again the next time the object is about to be | ||||||
|  |  * freed. | ||||||
|  |  * | ||||||
|  |  * Note that if a destructor already exists for the given key, it will be | ||||||
|  |  * replaced without calling it. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The object onto which to register the destructor | ||||||
|  |  * @param key An arbitrary value that can be later used to unregister the | ||||||
|  |  * destructor | ||||||
|  |  * @param callback The destructor itself | ||||||
|  |  * @param user_data Extra data to pass to the destructor | ||||||
|  |  * @return True on success, false on failure. On failure, nothing is registered. | ||||||
|  |  */ | ||||||
|  | bool refcount_context_add_destructor(const RefcountContext *ctx, void *obj, | ||||||
|  |                                      void *key, | ||||||
|  |                                      refcount_destructor_callback_t callback, | ||||||
|  |                                      void *user_data) { | ||||||
|  |     struct DestructorEntry *entry = | ||||||
|  |         refcount_malloc(&ctx->alloc, sizeof(struct DestructorEntry)); | ||||||
|  |     if (!entry) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     entry->callback = callback; | ||||||
|  |     entry->user_data = user_data; | ||||||
|  |     if (!ht_insert(ENTRY->destructors, key, entry)) { | ||||||
|  |         refcount_free(&ctx->alloc, entry); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Unregister a destructor from the given object. The destructor will not be | ||||||
|  |  * called. If no destructor exists for the given key, this will do nothing. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The object for which to unregister the destructor | ||||||
|  |  * @param key The destructors key | ||||||
|  |  * @return True on success, false on error. On error, nothing is unregistered. | ||||||
|  |  */ | ||||||
|  | bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj, | ||||||
|  |                                         void *key) { | ||||||
|  |     return ht_remove(ENTRY->destructors, key); | ||||||
|  | } | ||||||
|  | |||||||
| @ -32,11 +32,23 @@ void destroy_callback(void *a_raw, void *ignored) { | |||||||
|     counting_free(a); |     counting_free(a); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void reref_destructor(void *a, void *ctx_raw) { | ||||||
|  |     const RefcountContext *ctx = ctx_raw; | ||||||
|  |     refcount_context_ref(ctx, a); | ||||||
|  | } | ||||||
|  |  | ||||||
| int main(int argc, const char **argv) { | int main(int argc, const char **argv) { | ||||||
|     RefcountContext *c = |     RefcountContext *c = | ||||||
|         refcount_make_context(offsetof(A, refcount), held_refs_callback, |         refcount_make_context(offsetof(A, refcount), held_refs_callback, | ||||||
|                               destroy_callback, NULL, &COUNTING_ALLOCATOR); |                               destroy_callback, NULL, &COUNTING_ALLOCATOR); | ||||||
|  |  | ||||||
|  |     A static_a = { | ||||||
|  |         .num = 0, | ||||||
|  |         .str = counting_strdup("static"), | ||||||
|  |         .next = make_a(c, 0, "in static"), | ||||||
|  |     }; | ||||||
|  |     refcount_context_init_static(c, &static_a); | ||||||
|  |  | ||||||
|     A *a = make_a(c, 10, "Hello world\n"); |     A *a = make_a(c, 10, "Hello world\n"); | ||||||
|     assert(!refcount_context_is_static(c, a)); |     assert(!refcount_context_is_static(c, a)); | ||||||
|     assert(refcount_context_num_refs(c, a) == 0); |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
| @ -148,6 +160,20 @@ int main(int argc, const char **argv) { | |||||||
|     refcount_context_destroy_weakref(c, w); |     refcount_context_destroy_weakref(c, w); | ||||||
|     refcount_context_destroy_weakref(c, x); |     refcount_context_destroy_weakref(c, x); | ||||||
|  |  | ||||||
|  |     a = make_a(c, 10, "test destructor"); | ||||||
|  |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
|  |     int key; | ||||||
|  |     assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c)); | ||||||
|  |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
|  |     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_destroy(c); |     refcount_context_destroy(c); | ||||||
|  |  | ||||||
|     check_allocator_status(); |     check_allocator_status(); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user