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(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)
|
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;
|
||||||
}
|
}
|
||||||
|
268
src/refcount.c
268
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;
|
return false;
|
||||||
}
|
}
|
||||||
clear_weak_references(ctx, obj);
|
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 = refcount_list_remove_full(
|
||||||
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
||||||
refcount_list_free_with_data_full(
|
refcount_list_free_with_data_full(
|
||||||
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
||||||
return true;
|
success = true;
|
||||||
} else {
|
end:
|
||||||
BREAKPOINT(DEINIT_STATIC, ctx, obj)
|
unlock_mutex(&ctx->so_mtx);
|
||||||
return false;
|
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) {
|
||||||
|
if (lock_mutex(&ctx->gr_mtx)) {
|
||||||
ctx->gc_roots = refcount_list_remove_full(
|
ctx->gc_roots = refcount_list_remove_full(
|
||||||
ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc);
|
ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc);
|
||||||
ENTRY->impl.counted.gc_root = NULL;
|
ENTRY->impl.counted.gc_root = NULL;
|
||||||
|
unlock_mutex(&ctx->gr_mtx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -246,14 +323,23 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
if (!lock_entry_mtx(ENTRY)) {
|
||||||
|
// if this fails, we prefer a memory leak to causing undefined behavior
|
||||||
|
// 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;
|
--ENTRY->impl.counted.ref_count;
|
||||||
track_gc_root(ctx, obj);
|
track_gc_root(ctx, obj);
|
||||||
|
unlock_entry_mtx(ENTRY);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pair of a context and double pointer to a queue. This is for internal use.
|
* A pair of a context and double pointer to a queue. This is for internal use.
|
||||||
@ -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);
|
|
||||||
}
|
}
|
||||||
refcount_free(&ctx->alloc, wr);
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
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