Add multi-thread support
This commit is contained in:
		| @ -1,12 +1,23 @@ | |||||||
| cmake_minimum_required(VERSION 3.11) | cmake_minimum_required(VERSION 3.11) | ||||||
|  |  | ||||||
| set(CMAKE_C_STANDARD 99) | set(REFCOUNT_USE_THREADS | ||||||
|  |     ON | ||||||
|  |     CACHE BOOL "Weather or not RefCount should be thread aware.") | ||||||
|  |  | ||||||
|  | if(REFCOUNT_USE_THREADS) | ||||||
|  |   set(CMAKE_C_STANDARD 11) | ||||||
|  | else() | ||||||
|  |   set(CMAKE_C_STANDARD 99) | ||||||
|  | endif() | ||||||
|  |  | ||||||
| project( | project( | ||||||
|   refcount |   refcount | ||||||
|   VERSION 1.0 |   VERSION 1.0 | ||||||
|   LANGUAGES C) |   LANGUAGES C) | ||||||
|  |  | ||||||
|  | # For configuring config.h (I like this name better) | ||||||
|  | set(REFCOUNT_HAS_THREADS "${REFCOUNT_USE_THREADS}") | ||||||
|  |  | ||||||
| include(FetchContent) | include(FetchContent) | ||||||
| FetchContent_Declare( | FetchContent_Declare( | ||||||
|   ht |   ht | ||||||
| @ -19,13 +30,17 @@ 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) | ||||||
|  |  | ||||||
| add_library(refcount src/allocator.c src/list.c src/refcount.c) | add_library(refcount src/allocator.c src/list.c src/refcount.c) | ||||||
| target_link_libraries(refcount PUBLIC ht) | target_link_libraries(refcount PUBLIC ht) | ||||||
| target_include_directories(refcount PUBLIC include/) | target_include_directories(refcount PUBLIC include/) | ||||||
| target_compile_options(refcount PRIVATE -Wall -Wpedantic) | target_compile_options(refcount PRIVATE -Wall -Wpedantic) | ||||||
|  | target_include_directories(refcount | ||||||
|  |                            PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include") | ||||||
|  |  | ||||||
| if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) | ||||||
|   add_subdirectory(test/) |   add_subdirectory(test/) | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							| @ -1,13 +1,30 @@ | |||||||
| # RefCount | # RefCount | ||||||
|  |  | ||||||
| RefCount is a reference counting (thus the name) and garbage collection library | RefCount is a reference counting (thus the name) and garbage collection library | ||||||
| written in standard C99. | written in C. | ||||||
|  |  | ||||||
|  | RefCount has support for | ||||||
|  |  - 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 | ||||||
|  |  | ||||||
| ### Building | ### Building | ||||||
|  |  | ||||||
|  | The only external requirement is [ht](https://git.zander.im/Zander671/ht). If | ||||||
|  | you build using the instructions below, CMake will automatically download and | ||||||
|  | build the dependency for you. | ||||||
|  |  | ||||||
|  | For multi threading support, you need a C11 compiler that supports atomic types | ||||||
|  | and has the `<threads.h>` header. Without threading support, you only need a C99 | ||||||
|  | compiler. | ||||||
|  |  | ||||||
| You can build using: | You can build using: | ||||||
| ```sh | ```sh | ||||||
| cmake -B build | cmake -B build . | ||||||
|  | # replace with above with | ||||||
|  | # cmake -B build -DREFCOUNT_USE_THREADS=OFF | ||||||
|  | # to build without threads (change the "OFF" to "ON" to re-enable threads) | ||||||
| make -C build | make -C build | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #define INCLUDED_REFCOUNT_ALLOCATOR_H | #define INCLUDED_REFCOUNT_ALLOCATOR_H | ||||||
|  |  | ||||||
| #include <ht.h> | #include <ht.h> | ||||||
|  | #include <refcount/config.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								include/refcount/config.h.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								include/refcount/config.h.in
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #ifndef INCLUDED_REFCOUNT_CONFIG_H | ||||||
|  | #define INCLUDED_REFCOUNT_CONFIG_H | ||||||
|  |  | ||||||
|  | #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 | ||||||
| @ -5,6 +5,7 @@ | |||||||
| #define INCLUDED_REFCOUNT_LIST_H | #define INCLUDED_REFCOUNT_LIST_H | ||||||
|  |  | ||||||
| #include <refcount/allocator.h> | #include <refcount/allocator.h> | ||||||
|  | #include <refcount/config.h> | ||||||
| #include <stdarg.h> | #include <stdarg.h> | ||||||
| #include <stddef.h> // for NULL | #include <stddef.h> // for NULL | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,10 +5,16 @@ | |||||||
| #define INCLUDED_REFCOUNT_REFCOUNT_H | #define INCLUDED_REFCOUNT_REFCOUNT_H | ||||||
|  |  | ||||||
| #include <ht.h> | #include <ht.h> | ||||||
|  | #include <refcount/config.h> | ||||||
| #include <refcount/list.h> | #include <refcount/list.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  |  | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  | #    include <stdatomic.h> | ||||||
|  | #    include <threads.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| @ -57,29 +63,12 @@ typedef struct RefcountContext { | |||||||
|     RefcountList *static_objects; //!< List of static objects registered with |     RefcountList *static_objects; //!< List of static objects registered with | ||||||
|                                   //!< this context. |                                   //!< this context. | ||||||
|     RefcountList *gc_roots; //!< List of garbage collector roots. |     RefcountList *gc_roots; //!< List of garbage collector roots. | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  |     mtx_t so_mtx; //<! Mutex protecting static_objects. | ||||||
|  |     mtx_t gr_mtx; //<! Mutex protecting gc_roots. | ||||||
|  | #endif | ||||||
| } RefcountContext; | } RefcountContext; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Enumeration of operations that may cause the debug breakpoint to be called. |  | ||||||
|  */ |  | ||||||
| typedef enum { |  | ||||||
|     REFCOUNT_OPER_DEINIT_STATIC, |  | ||||||
|     REFCOUNT_OPER_FLOAT, |  | ||||||
| } RefcountOperation; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Type for a function to be called when some kind of error occurs. It should |  | ||||||
|  * take a #RefcountOperation, a #RefcountContext pointer, and the object on |  | ||||||
|  * which the error occurred. |  | ||||||
|  * @param oper The operation that failed |  | ||||||
|  * @param ctx The context the operation was performed on |  | ||||||
|  * @param obj The object that caused the failure |  | ||||||
|  */ |  | ||||||
| typedef void (*refcount_debug_callback_t)(RefcountOperation oper, |  | ||||||
|                                           RefcountContext *ctx, void *obj); |  | ||||||
|  |  | ||||||
| extern refcount_debug_callback_t refcount_debug_breakpoint; |  | ||||||
|  |  | ||||||
| extern RefcountContext *refcount_default_context; | extern RefcountContext *refcount_default_context; | ||||||
|  |  | ||||||
| RefcountContext * | RefcountContext * | ||||||
| @ -90,6 +79,20 @@ 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 | ||||||
|  |  * reference that does not prevent an object from being de-allocated. | ||||||
|  |  */ | ||||||
|  | typedef struct RefcountWeakref { | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  |     _Atomic uint64_t ref_count; //!< Reference count of this weakref (atomic). | ||||||
|  |     mtx_t mtx; //!< Mutex protecting both the weakref and it's object. | ||||||
|  | #else | ||||||
|  |     uint64_t ref_count; //!< Reference count of this weakref. | ||||||
|  | #endif | ||||||
|  |     void *data; //<! The object this weak ref references (not the entry). | ||||||
|  | } RefcountWeakref; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Opaque struct holding reference data for an object. This should be included |  * 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. |  * as a non-pointer member of the struct you are trying to track, e.g. | ||||||
| @ -103,8 +106,7 @@ void refcount_context_destroy(RefcountContext *ctx); | |||||||
|  */ |  */ | ||||||
| typedef struct RefcountEntry { | typedef struct RefcountEntry { | ||||||
|     bool is_static; //!< Whether the object is static. |     bool is_static; //!< Whether the object is static. | ||||||
|     RefcountList *weak_refs; //<! List of #RefcountWeakref structures for |     RefcountWeakref *weak_ref; //<! Weakref for this entry. | ||||||
|                              //<! holding thread safe weak references. |  | ||||||
|     union { |     union { | ||||||
|         struct { |         struct { | ||||||
|             uint64_t ref_count; //!< The object's reference count. |             uint64_t ref_count; //!< The object's reference count. | ||||||
| @ -115,16 +117,6 @@ typedef struct RefcountEntry { | |||||||
|     } impl; |     } impl; | ||||||
| } RefcountEntry; | } RefcountEntry; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Structure holding the required information for a weak reference. That is, a |  | ||||||
|  * reference that does not prevent an object from being de-allocated. |  | ||||||
|  */ |  | ||||||
| typedef struct RefcountWeakref { |  | ||||||
|     RefcountList *entry; //!< Link in the #RefcountEntry list of the object this |  | ||||||
|                          //!< references that points to this reference. |  | ||||||
|     void *data; //<! The object this weak ref references (not the entry). |  | ||||||
| } RefcountWeakref; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Return a pointer to the entry associated with an object. |  * Return a pointer to the entry associated with an object. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
| @ -154,12 +146,12 @@ static inline bool refcount_context_is_static(const RefcountContext *ctx, | |||||||
|  * @param obj The object |  * @param obj The object | ||||||
|  * @return The number of references the object has |  * @return The number of references the object has | ||||||
|  */ |  */ | ||||||
| static inline size_t refcount_context_num_refs(const RefcountContext *ctx, | static inline uint64_t refcount_context_num_refs(const RefcountContext *ctx, | ||||||
|                                                void *obj) { |                                                  void *obj) { | ||||||
|     return REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count; |     return REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count; | ||||||
| } | } | ||||||
|  |  | ||||||
| void refcount_context_init_obj(const RefcountContext *ctx, void *obj); | bool refcount_context_init_obj(const RefcountContext *ctx, void *obj); | ||||||
|  |  | ||||||
| bool refcount_context_init_static(RefcountContext *ctx, void *obj); | bool refcount_context_init_static(RefcountContext *ctx, void *obj); | ||||||
|  |  | ||||||
| @ -199,19 +191,11 @@ RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, | |||||||
| void refcount_context_destroy_weakref(const RefcountContext *ctx, | void refcount_context_destroy_weakref(const RefcountContext *ctx, | ||||||
|                                       RefcountWeakref *wr); |                                       RefcountWeakref *wr); | ||||||
|  |  | ||||||
| /** | bool refcount_context_weakref_is_valid(const RefcountContext *ctx, | ||||||
|  * Return weather the object referenced by a weak reference still exists. |                                        RefcountWeakref *wr); | ||||||
|  * @param ctx The #RefcountContext |  | ||||||
|  * @param wr The weak reference |  | ||||||
|  * @return Weather the reference is still valid |  | ||||||
|  */ |  | ||||||
| static inline bool refcount_context_weakref_is_valid(const RefcountContext *ctx, |  | ||||||
|                                                      RefcountWeakref *wr) { |  | ||||||
|     return wr->data; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void *refcount_context_ref_weakref(const RefcountContext *ctx, | void *refcount_context_ref_weakref(const RefcountContext *ctx, | ||||||
|                                    const RefcountWeakref *wr); |                                    RefcountWeakref *wr); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Convenience function that calls #refcount_context_ref_weakref followed by |  * Convenience function that calls #refcount_context_ref_weakref followed by | ||||||
| @ -238,9 +222,6 @@ static inline void *refcount_context_strengthen(const RefcountContext *ctx, | |||||||
| static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx, | static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx, | ||||||
|                                                        void *obj) { |                                                        void *obj) { | ||||||
|     RefcountWeakref *wr = refcount_context_make_weakref(ctx, obj); |     RefcountWeakref *wr = refcount_context_make_weakref(ctx, obj); | ||||||
|     if (!wr) { |  | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
|     refcount_context_unref(ctx, obj); |     refcount_context_unref(ctx, obj); | ||||||
|     return wr; |     return wr; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										288
									
								
								src/refcount.c
									
									
									
									
									
								
							
							
						
						
									
										288
									
								
								src/refcount.c
									
									
									
									
									
								
							| @ -10,22 +10,31 @@ | |||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Callback to be executed when a program error occurred. A sane to default is |  * Internal magic macro. | ||||||
|  * to print a message and then abort. This is not used to report normal |  | ||||||
|  * recoverable errors, but rather to report fatal logic errors. |  | ||||||
|  */ |  */ | ||||||
| refcount_debug_callback_t refcount_debug_breakpoint = NULL; | #define ENTRY (REFCOUNT_OBJECT_ENTRY(ctx, obj)) | ||||||
|  |  | ||||||
| /** | // Some utility macros for threads | ||||||
|  * Call the breakpoint handler if one is installed. | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  * @param oper The failing operation | #    define lock_mutex(m)   (mtx_lock(m) == thrd_success) | ||||||
|  * @param ctx The #RefcountContext | #    define unlock_mutex(m) (mtx_unlock(m)) | ||||||
|  * @param obj The object on which the operation tried to act | #    define store_wr_count(obj, c) \ | ||||||
|  */ |         atomic_store_explicit(obj, (c), memory_order_relaxed) | ||||||
| #define BREAKPOINT(oper, ctx, obj)                                     \ | #    define inc_wr_count(obj) \ | ||||||
|     if (refcount_debug_breakpoint) {                                   \ |         (atomic_fetch_add_explicit(obj, 1, memory_order_relaxed)) | ||||||
|         refcount_debug_breakpoint(REFCOUNT_OPER_##oper, (ctx), (obj)); \ | #    define dec_wr_count(obj) \ | ||||||
|     } |         (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)) | ||||||
|  | #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 | ||||||
|  | #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 | ||||||
| @ -56,6 +65,17 @@ refcount_make_context(size_t entry_offset, | |||||||
|     if (!ctx) { |     if (!ctx) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  |     if (mtx_init(&ctx->so_mtx, mtx_plain) != thrd_success) { | ||||||
|  |         refcount_free(alloc, ctx); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |     if (mtx_init(&ctx->gr_mtx, mtx_recursive) != thrd_success) { | ||||||
|  |         refcount_free(alloc, ctx); | ||||||
|  |         mtx_destroy(&ctx->so_mtx); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|     ctx->entry_offset = entry_offset; |     ctx->entry_offset = entry_offset; | ||||||
|     ctx->held_refs_callback = held_refs_callback; |     ctx->held_refs_callback = held_refs_callback; | ||||||
|     ctx->destroy_callback = destroy_callback; |     ctx->destroy_callback = destroy_callback; | ||||||
| @ -79,28 +99,55 @@ void refcount_context_destroy(RefcountContext *ctx) { | |||||||
|         ctx->static_objects, refcount_context_deinit_static_as_callback, ctx, |         ctx->static_objects, refcount_context_deinit_static_as_callback, ctx, | ||||||
|         &ctx->alloc); |         &ctx->alloc); | ||||||
|  |  | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  |     mtx_destroy(&ctx->so_mtx); | ||||||
|  |     mtx_destroy(&ctx->gr_mtx); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     refcount_context_garbage_collect(ctx); |     refcount_context_garbage_collect(ctx); | ||||||
|  |  | ||||||
|     refcount_free(&ctx->alloc, ctx); |     refcount_free(&ctx->alloc, ctx); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Internal magic macro. |  * Initialize the weak_ref field on an object. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The object | ||||||
|  |  * @return True on success | ||||||
|  */ |  */ | ||||||
| #define ENTRY (REFCOUNT_OBJECT_ENTRY(ctx, obj)) | static bool init_obj_weakref(const RefcountContext *ctx, void *obj) { | ||||||
|  |     ENTRY->weak_ref = refcount_malloc(&ctx->alloc, sizeof(RefcountWeakref)); | ||||||
|  |     if (!ENTRY->weak_ref) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     ENTRY->weak_ref->data = obj; | ||||||
|  | #ifdef REFCOUNT_HAS_THREADS | ||||||
|  |     if (mtx_init(&ENTRY->weak_ref->mtx, mtx_recursive) != thrd_success) { | ||||||
|  |         refcount_free(&ctx->alloc, ENTRY->weak_ref); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     store_wr_count(&ENTRY->weak_ref->ref_count, 1); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Initialize the #RefcountEntry member of an object. |  * Initialize the #RefcountEntry member of an object. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
|  * @param obj The object to initialize |  * @param obj The object to initialize | ||||||
|  |  * @return True on success, false on failure. Note that you don't have to do | ||||||
|  |  * anything special to clean up the #RefcountEntry structure on failure and it | ||||||
|  |  * is safe to call this a second time on the same object (though it will | ||||||
|  |  * probably fail for the same reason). | ||||||
|  */ |  */ | ||||||
| void refcount_context_init_obj(const RefcountContext *ctx, void *obj) { | bool refcount_context_init_obj(const RefcountContext *ctx, void *obj) { | ||||||
|     if (obj) { |     if (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; | ||||||
|         ENTRY->weak_refs = NULL; |         init_obj_weakref(ctx, obj); | ||||||
|     } |     } | ||||||
|  |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -110,15 +157,25 @@ void refcount_context_init_obj(const RefcountContext *ctx, void *obj) { | |||||||
|  * @return True on success, false otherwise |  * @return True on success, false otherwise | ||||||
|  */ |  */ | ||||||
| bool refcount_context_init_static(RefcountContext *ctx, void *obj) { | bool refcount_context_init_static(RefcountContext *ctx, void *obj) { | ||||||
|     ENTRY->is_static = true; |     if (!lock_mutex(&ctx->so_mtx)) { | ||||||
|     ctx->static_objects = |  | ||||||
|         refcount_list_push_full(ctx->static_objects, obj, &ctx->alloc); |  | ||||||
|     if (!ctx->static_objects) { |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |     bool success = false; | ||||||
|  |     ENTRY->is_static = true; | ||||||
|  |     if (!init_obj_weakref(ctx, obj)) { | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|  |     RefcountList *new_static_objects = | ||||||
|  |         refcount_list_push_full(ctx->static_objects, obj, &ctx->alloc); | ||||||
|  |     if (!new_static_objects) { | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|  |     ctx->static_objects = new_static_objects; | ||||||
|     ENTRY->impl.static_entry = ctx->static_objects; |     ENTRY->impl.static_entry = ctx->static_objects; | ||||||
|     ENTRY->weak_refs = NULL; |     success = true; | ||||||
|     return true; | end: | ||||||
|  |     unlock_mutex(&ctx->so_mtx); | ||||||
|  |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -137,24 +194,18 @@ static inline bool obj_held_refs(RefcountContext *ctx, void *obj, | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Call back used from #clear_weak_references. |  * Remove a reference from a weakref, possibly freeing it if it's reference | ||||||
|  * @param wr_raw The #RefcountWeakref object |  * count falls to zero. | ||||||
|  */ |  | ||||||
| static void clear_weak_reference_callback(void *wr_raw) { |  | ||||||
|     RefcountWeakref *wr = wr_raw; |  | ||||||
|     wr->entry = NULL; |  | ||||||
|     wr->data = NULL; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Clear all weak references of an object without freeing them. |  | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
|  * @param obj The object to clear |  * @param wr The weak reference | ||||||
|  */ |  */ | ||||||
| static inline void clear_weak_references(const RefcountContext *ctx, | static void unref_weakref(const RefcountContext *ctx, RefcountWeakref *wr) { | ||||||
|                                          void *obj) { |     if (dec_wr_count(&wr->ref_count) == 1) { | ||||||
|     refcount_list_free_full(ENTRY->weak_refs, clear_weak_reference_callback, | #ifdef REFCOUNT_HAS_THREADS | ||||||
|                             &ctx->alloc); |         mtx_destroy(&wr->mtx); | ||||||
|  | #endif | ||||||
|  |         refcount_free(&ctx->alloc, wr); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -164,21 +215,32 @@ static inline void clear_weak_references(const RefcountContext *ctx, | |||||||
|  * @return True on success, false otherwise |  * @return True on success, false otherwise | ||||||
|  */ |  */ | ||||||
| bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { | bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { | ||||||
|     if (refcount_context_is_static(ctx, obj)) { |     if (!lock_mutex(&ctx->so_mtx)) { | ||||||
|         RefcountList *held_refs = NULL; |  | ||||||
|         if (!obj_held_refs(ctx, obj, &held_refs)) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         clear_weak_references(ctx, obj); |  | ||||||
|         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); |  | ||||||
|         return true; |  | ||||||
|     } else { |  | ||||||
|         BREAKPOINT(DEINIT_STATIC, ctx, obj) |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |     bool success = false; | ||||||
|  |     if (!refcount_context_is_static(ctx, obj)) { | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|  |     RefcountList *held_refs = NULL; | ||||||
|  |     if (!obj_held_refs(ctx, obj, &held_refs)) { | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|  |     if (!lock_entry_mtx(ENTRY)) { | ||||||
|  |         refcount_list_free_full(held_refs, NULL, &ctx->alloc); | ||||||
|  |         goto end; | ||||||
|  |     } | ||||||
|  |     ENTRY->weak_ref->data = NULL; | ||||||
|  |     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); | ||||||
|  |     refcount_list_free_with_data_full( | ||||||
|  |         held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc); | ||||||
|  |     success = true; | ||||||
|  | end: | ||||||
|  |     unlock_mutex(&ctx->so_mtx); | ||||||
|  |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -190,9 +252,14 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { | |||||||
| void *refcount_context_ref(const RefcountContext *ctx, void *obj) { | void *refcount_context_ref(const RefcountContext *ctx, void *obj) { | ||||||
|     if (!obj) { |     if (!obj) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } else if (!ENTRY->is_static) { |     } | ||||||
|  |     if (!lock_entry_mtx(ENTRY)) { | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |     if (!ENTRY->is_static) { | ||||||
|         ++ENTRY->impl.counted.ref_count; |         ++ENTRY->impl.counted.ref_count; | ||||||
|     } |     } | ||||||
|  |     unlock_entry_mtx(ENTRY); | ||||||
|     return obj; |     return obj; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -204,15 +271,22 @@ void *refcount_context_ref(const RefcountContext *ctx, void *obj) { | |||||||
|  * @return True on success |  * @return True on success | ||||||
|  */ |  */ | ||||||
| static bool track_gc_root(RefcountContext *ctx, void *obj) { | static bool track_gc_root(RefcountContext *ctx, void *obj) { | ||||||
|  |     if (!lock_mutex(&ctx->gr_mtx)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     bool success = false; | ||||||
|     if (!ENTRY->impl.counted.gc_root) { |     if (!ENTRY->impl.counted.gc_root) { | ||||||
|         ctx->gc_roots = |         ctx->gc_roots = | ||||||
|             refcount_list_push_full(ctx->gc_roots, obj, &ctx->alloc); |             refcount_list_push_full(ctx->gc_roots, obj, &ctx->alloc); | ||||||
|         if (!ctx->gc_roots) { |         if (!ctx->gc_roots) { | ||||||
|             return false; |             goto end; | ||||||
|         } |         } | ||||||
|         ENTRY->impl.counted.gc_root = ctx->gc_roots; |         ENTRY->impl.counted.gc_root = ctx->gc_roots; | ||||||
|     } |     } | ||||||
|     return true; |     success = true; | ||||||
|  | end: | ||||||
|  |     unlock_mutex(&ctx->gr_mtx); | ||||||
|  |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -222,9 +296,12 @@ static bool track_gc_root(RefcountContext *ctx, void *obj) { | |||||||
|  * @param obj The object to untrack |  * @param obj The object to untrack | ||||||
|  */ |  */ | ||||||
| static void remove_gc_root(RefcountContext *ctx, void *obj) { | static void remove_gc_root(RefcountContext *ctx, void *obj) { | ||||||
|     ctx->gc_roots = refcount_list_remove_full( |     if (lock_mutex(&ctx->gr_mtx)) { | ||||||
|         ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc); |         ctx->gc_roots = refcount_list_remove_full( | ||||||
|     ENTRY->impl.counted.gc_root = NULL; |             ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc); | ||||||
|  |         ENTRY->impl.counted.gc_root = NULL; | ||||||
|  |         unlock_mutex(&ctx->gr_mtx); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -246,13 +323,22 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj, | |||||||
|         return obj; |         return obj; | ||||||
|     } else if (obj == toplevel) { |     } else if (obj == toplevel) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } else if (ENTRY->impl.counted.ref_count <= 1) { |  | ||||||
|         *queue = refcount_list_push_full(*queue, obj, &ctx->alloc); |  | ||||||
|         return NULL; |  | ||||||
|     } |     } | ||||||
|     --ENTRY->impl.counted.ref_count; |     if (!lock_entry_mtx(ENTRY)) { | ||||||
|     track_gc_root(ctx, obj); |         // if this fails, we prefer a memory leak to causing undefined behavior | ||||||
|     return obj; |         // and possibly crashing | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |     if (ENTRY->impl.counted.ref_count <= 1) { | ||||||
|  |         *queue = refcount_list_push_full(*queue, obj, &ctx->alloc); | ||||||
|  |         unlock_entry_mtx(ENTRY); | ||||||
|  |         return NULL; | ||||||
|  |     } else { | ||||||
|  |         --ENTRY->impl.counted.ref_count; | ||||||
|  |         track_gc_root(ctx, obj); | ||||||
|  |         unlock_entry_mtx(ENTRY); | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -282,8 +368,13 @@ static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) { | |||||||
|  * @param obj The object to destroy |  * @param obj The object to destroy | ||||||
|  */ |  */ | ||||||
| static inline void destroy_object(RefcountContext *ctx, void *obj) { | static inline void destroy_object(RefcountContext *ctx, void *obj) { | ||||||
|     clear_weak_references(ctx, obj); |     if (!lock_entry_mtx(ENTRY)) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|     remove_gc_root(ctx, obj); |     remove_gc_root(ctx, obj); | ||||||
|  |     ENTRY->weak_ref->data = NULL; | ||||||
|  |     unlock_entry_mtx(ENTRY); | ||||||
|  |     unref_weakref(ctx, ENTRY->weak_ref); | ||||||
|     if (ctx->destroy_callback) { |     if (ctx->destroy_callback) { | ||||||
|         ctx->destroy_callback(obj, ctx->user_data); |         ctx->destroy_callback(obj, ctx->user_data); | ||||||
|     } |     } | ||||||
| @ -351,15 +442,16 @@ void *refcount_context_float(RefcountContext *ctx, void *obj) { | |||||||
|         return NULL; |         return NULL; | ||||||
|     } else if (ENTRY->is_static) { |     } else if (ENTRY->is_static) { | ||||||
|         return obj; |         return obj; | ||||||
|     } else if (!ENTRY->impl.counted.ref_count) { |     } | ||||||
|         BREAKPOINT(FLOAT, ctx, obj); |     if (!lock_entry_mtx(ENTRY)) { | ||||||
|         return obj; |         return NULL; | ||||||
|     } |     } | ||||||
|     if (--ENTRY->impl.counted.ref_count) { |     if (--ENTRY->impl.counted.ref_count) { | ||||||
|         track_gc_root(ctx, obj); |         track_gc_root(ctx, obj); | ||||||
|     } else { |     } else { | ||||||
|         remove_gc_root(ctx, obj); |         remove_gc_root(ctx, obj); | ||||||
|     } |     } | ||||||
|  |     unlock_entry_mtx(ENTRY); | ||||||
|     return obj; |     return obj; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -475,6 +567,9 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) { | |||||||
|         // no loops possible |         // no loops possible | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  |     if (!lock_mutex(&ctx->gr_mtx)) { | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|     ptrdiff_t total_cleared = 0; |     ptrdiff_t total_cleared = 0; | ||||||
|     RefcountList *root = ctx->gc_roots; |     RefcountList *root = ctx->gc_roots; | ||||||
|     while (root) { |     while (root) { | ||||||
| @ -484,6 +579,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) { | |||||||
|         } |         } | ||||||
|         total_cleared += res; |         total_cleared += res; | ||||||
|     } |     } | ||||||
|  |     unlock_mutex(&ctx->gr_mtx); | ||||||
|     return total_cleared; |     return total_cleared; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -495,25 +591,12 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) { | |||||||
|  * NULL to indicate that the referenced object is no longer in existence. |  * NULL to indicate that the referenced object is no longer in existence. | ||||||
|  * @param ctx The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
|  * @param obj The object for which to create a weak reference |  * @param obj The object for which to create a weak reference | ||||||
|  * @return The newly created weak reference, or NULL if an allocated error |  * @return The newly created weak reference | ||||||
|  * occurred. |  | ||||||
|  */ |  */ | ||||||
| RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, | RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, | ||||||
|                                                void *obj) { |                                                void *obj) { | ||||||
|     RefcountWeakref *wr = refcount_malloc(&ctx->alloc, sizeof(RefcountWeakref)); |     inc_wr_count(&ENTRY->weak_ref->ref_count); | ||||||
|     if (!wr) { |     return ENTRY->weak_ref; | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
|     RefcountList *new_weak_refs = |  | ||||||
|         refcount_list_push_full(ENTRY->weak_refs, wr, &ctx->alloc); |  | ||||||
|     if (!new_weak_refs) { |  | ||||||
|         refcount_free(&ctx->alloc, wr); |  | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
|     ENTRY->weak_refs = new_weak_refs; |  | ||||||
|     wr->entry = new_weak_refs; |  | ||||||
|     wr->data = obj; |  | ||||||
|     return wr; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -524,12 +607,24 @@ RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, | |||||||
|  */ |  */ | ||||||
| void refcount_context_destroy_weakref(const RefcountContext *ctx, | void refcount_context_destroy_weakref(const RefcountContext *ctx, | ||||||
|                                       RefcountWeakref *wr) { |                                       RefcountWeakref *wr) { | ||||||
|     if (wr->data) { |     unref_weakref(ctx, wr); | ||||||
|         void *obj = wr->data; | } | ||||||
|         ENTRY->weak_refs = refcount_list_remove_full( |  | ||||||
|             ENTRY->weak_refs, wr->entry, NULL, &ctx->alloc); | /** | ||||||
|  |  * Return weather the object referenced by a weak reference still exists. | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param wr The weak reference | ||||||
|  |  * @return Weather the reference is still valid | ||||||
|  |  */ | ||||||
|  | bool refcount_context_weakref_is_valid(const RefcountContext *ctx, | ||||||
|  |                                        RefcountWeakref *wr) { | ||||||
|  |     // we need the locks because accessing the data member is not atomic | ||||||
|  |     if (!lock_mutex(&wr->mtx)) { | ||||||
|  |         return NULL; // we can't be sure, so play it safe | ||||||
|     } |     } | ||||||
|     refcount_free(&ctx->alloc, wr); |     bool is_valid = wr->data; | ||||||
|  |     unlock_mutex(&wr->mtx); | ||||||
|  |     return is_valid; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -540,9 +635,14 @@ void refcount_context_destroy_weakref(const RefcountContext *ctx, | |||||||
|  * @return The newly referenced object, or NULL |  * @return The newly referenced object, or NULL | ||||||
|  */ |  */ | ||||||
| void *refcount_context_ref_weakref(const RefcountContext *ctx, | void *refcount_context_ref_weakref(const RefcountContext *ctx, | ||||||
|                                    const RefcountWeakref *wr) { |                                    RefcountWeakref *wr) { | ||||||
|     if (!wr->data) { |     if (!lock_mutex(&wr->mtx)) { | ||||||
|         return NULL; |         return NULL; // we can't be sure, so play it safe | ||||||
|     } |     } | ||||||
|     return refcount_context_ref(ctx, wr->data); |     void *obj = NULL; | ||||||
|  |     if (wr->data) { | ||||||
|  |         obj = refcount_context_ref(ctx, wr->data); | ||||||
|  |     } | ||||||
|  |     unlock_mutex(&wr->mtx); | ||||||
|  |     return obj; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | #undef NDEBUG | ||||||
| #include "alloc.h" | #include "alloc.h" | ||||||
|  |  | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
| @ -130,7 +131,6 @@ int main(int argc, const char **argv) { | |||||||
|     RefcountWeakref *x = refcount_context_weaken(c, a); |     RefcountWeakref *x = refcount_context_weaken(c, a); | ||||||
|     w = refcount_context_make_weakref(c, a); |     w = refcount_context_make_weakref(c, a); | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     assert(refcount_context_num_refs(c, a) == 1); | ||||||
|     assert(w != x); |  | ||||||
|     refcount_context_destroy_weakref(c, x); |     refcount_context_destroy_weakref(c, x); | ||||||
|  |  | ||||||
|     refcount_context_ref(c, a); |     refcount_context_ref(c, a); | ||||||
| @ -138,7 +138,6 @@ int main(int argc, const char **argv) { | |||||||
|  |  | ||||||
|     x = refcount_context_weaken(c, a); |     x = refcount_context_weaken(c, a); | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     assert(refcount_context_num_refs(c, a) == 1); | ||||||
|     assert(w != x); |  | ||||||
|     assert(refcount_context_weakref_is_valid(c, w)); |     assert(refcount_context_weakref_is_valid(c, w)); | ||||||
|     assert(refcount_context_weakref_is_valid(c, x)); |     assert(refcount_context_weakref_is_valid(c, x)); | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user