diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml index 26cdafb67..e0e8ba233 100644 --- a/docs/reference/glib/glib-docs.xml +++ b/docs/reference/glib/glib-docs.xml @@ -120,6 +120,9 @@ + + + diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 01da779e8..1717ba37d 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3466,3 +3466,38 @@ g_atomic_ref_count_inc g_atomic_ref_count_dec g_atomic_ref_count_compare + +
+rcbox +g_rc_box_alloc +g_rc_box_alloc0 +g_rc_box_new +g_rc_box_new0 +g_rc_box_dup +g_rc_box_acquire +g_rc_box_release +g_rc_box_release_full +g_rc_box_get_size +
+ +
+arcbox +g_arc_box_alloc +g_arc_box_alloc0 +g_arc_box_new +g_arc_box_new0 +g_arc_box_dup +g_arc_box_acquire +g_arc_box_release +g_arc_box_release_full +g_arc_box_get_size +
+ +
+refstring +g_ref_string_new +g_ref_string_new_intern +g_ref_string_acquire +g_ref_string_release +g_ref_string_length +
diff --git a/glib/Makefile.am b/glib/Makefile.am index 7252a67d0..c7adc1f0e 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -96,6 +96,7 @@ deprecated_sources = \ libglib_2_0_la_SOURCES = \ $(deprecated_sources) \ glib_probes.d \ + garcbox.c \ garray.c \ gasyncqueue.c \ gasyncqueueprivate.h \ @@ -149,7 +150,10 @@ libglib_2_0_la_SOURCES = \ gquark.c \ gqueue.c \ grand.c \ + grcbox.c \ + grcboxprivate.h \ grefcount.c \ + grefstring.c \ gregex.c \ gscanner.c \ gscripttable.h \ @@ -286,7 +290,9 @@ glibsubinclude_HEADERS = \ gquark.h \ gqueue.h \ grand.h \ + grcbox.h \ grefcount.h \ + grefstring.h \ gregex.h \ gscanner.h \ gsequence.h \ diff --git a/glib/garcbox.c b/glib/garcbox.c new file mode 100644 index 000000000..9c1bd8fe5 --- /dev/null +++ b/glib/garcbox.c @@ -0,0 +1,373 @@ +/* garcbox.c: Atomically reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "config.h" + +#include "grcboxprivate.h" + +#include "gmessages.h" +#include "grefcount.h" + +#ifdef ENABLE_VALGRIND +#include "valgrind.h" +#endif + +#include "glib_trace.h" + +#include + +#define G_ARC_BOX(p) (GArcBox *) (((char *) (p)) - G_ARC_BOX_SIZE) + +/** + * SECTION:arcbox + * @Title: Atomically reference counted data + * @Short_description: Allocated memory with atomic reference counting semantics + * + * An "atomically reference counted box", or "ArcBox", is an opaque wrapper + * data type that is guaranteed to be as big as the size of a given data type, + * and which augments the given data type with thread safe reference counting + * semantics for its memory management. + * + * ArcBox is useful if you have a plain old data type, like a structure + * typically placed on the stack, and you wish to provide additional API + * to use it on the heap; or if you want to implement a new type to be + * passed around by reference without necessarily implementing copy/free + * semantics or your own reference counting. + * + * The typical use is: + * + * |[ + * typedef struct { + * char *name; + * char *address; + * char *city; + * char *state; + * int age; + * } Person; + * + * Person * + * person_new (void) + * { + * return g_atomic_rc_box_new0 (Person); + * } + * ]| + * + * Every time you wish to acquire a reference on the memory, you should + * call g_atomic_rc_box_acquire(); similarly, when you wish to release a reference + * you should call g_atomic_rc_box_release(): + * + * |[ + * // Add a Person to the Database; the Database acquires ownership + * // of the Person instance + * void + * add_person_to_database (Database *db, Person *p) + * { + * db->persons = g_list_prepend (db->persons, g_atomic_rc_box_acquire (p)); + * } + * + * // Removes a Person from the Database; the reference acquired by + * // add_person_to_database() is released here + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_atomic_rc_box_release (p); + * } + * ]| + * + * If you have additional memory allocated inside the structure, you can + * use g_atomic_rc_box_release_full(), which takes a function pointer, which + * will be called if the reference released was the last: + * + * |[ + * void + * person_clear (Person *p) + * { + * g_free (p->name); + * g_free (p->address); + * g_free (p->city); + * g_free (p->state); + * } + * + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_atomic_rc_box_release_full (p, (GDestroyNotify) person_clear); + * } + * ]| + * + * If you wish to transfer the ownership of a reference counted data + * type without increasing the reference count, you can use g_steal_pointer(): + * + * |[ + * Person *p = g_atomic_rc_box_new (Person); + * + * fill_person_details (p); + * + * add_person_to_database (db, g_steal_pointer (&p)); + * ]| + * + * ## Thread safety + * + * The reference counting operations on data allocated using g_atomic_rc_box_alloc(), + * g_atomic_rc_box_new(), and g_atomic_rc_box_dup() are guaranteed to be atomic, and thus + * can be safely be performed by different threads. It is important to note that + * only the reference acquisition and release are atomic; changes to the content + * of the data are your responsibility. + * + * ## Automatic pointer clean up + * + * If you want to add g_autoptr() support to your plain old data type through + * reference counting, you can use the G_DEFINE_AUTOPTR_CLEANUP_FUNC() and + * g_atomic_rc_box_release(): + * + * |[ + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, g_atomic_rc_box_release) + * ]| + * + * If you need to clear the contents of the data, you will need to use an + * ancillary function that calls g_rc_box_release_full(): + * + * |[ + * static void + * my_data_struct_release (MyDataStruct *data) + * { + * // my_data_struct_clear() is defined elsewhere + * g_atomic_rc_box_release_full (data, (GDestroyNotify) my_data_struct_clear); + * } + * + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, my_data_struct_clear) + * ]| + * + * Since: 2.58. + */ + +/** + * g_atomic_rc_box_alloc: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds atomic + * reference counting semantics to it. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_atomic_rc_box_alloc (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, TRUE, FALSE); +} + +/** + * g_atomic_rc_box_alloc0: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds atomic + * referenc counting semantics to it. + * + * The contents of the returned data is set to zero. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_atomic_rc_box_alloc0 (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, TRUE, TRUE); +} + +/** + * g_atomic_rc_box_new: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate atomically reference counted + * data with the size of the given @type. + * + * This macro calls g_atomic_rc_box_alloc() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_atomic_rc_box_new0: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate atomically reference counted + * data with the size of the given @type, and set its contents + * to zero. + * + * This macro calls g_atomic_rc_box_alloc0() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_atomic_rc_box_dup: + * @block_size: the number of bytes to copy, must be greater than 0 + * @mem_block: (not nullable): the memory to copy + * + * Allocates a new block of data with atomit reference counting + * semantics, and copies @block_size bytes of @mem_block + * into it. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory + * + * Since: 2.58 + */ +gpointer +(g_atomic_rc_box_dup) (gsize block_size, + gconstpointer mem_block) +{ + gpointer res; + + g_return_val_if_fail (block_size > 0, NULL); + g_return_val_if_fail (mem_block != NULL, NULL); + + res = g_rc_box_alloc_full (block_size, TRUE, FALSE); + memcpy (res, mem_block, block_size); + + return res; +} + +/** + * g_atomic_rc_box_acquire: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Atomically acquires a reference on the data pointed by @mem_block. + * + * Returns: (transfer full) (not nullable): a pointer to the data, + * with its reference count increased + * + * Since: 2.58 + */ +gpointer +(g_atomic_rc_box_acquire) (gpointer mem_block) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, NULL); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, NULL); +#endif + + g_atomic_ref_count_inc (&real_box->ref_count); + + TRACE (GLIB_RCBOX_ACQUIRE (mem_block, 1)); + + return mem_block; +} + +/** + * g_atomic_rc_box_release: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * + * Atomically releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_atomic_rc_box_release (gpointer mem_block) +{ + g_atomic_rc_box_release_full (mem_block, NULL); +} + +/** + * g_atomic_rc_box_release_full: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * @clear_func: (not nullable): a function to call when clearing the data + * + * Atomically releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will call @clear_func + * to clear the contents of @mem_block, and then will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_atomic_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_if_fail (mem_block != NULL); +#ifndef G_DISABLE_ASSERT + g_return_if_fail (real_box->magic == G_BOX_MAGIC); +#endif + + if (g_atomic_ref_count_dec (&real_box->ref_count)) + { + TRACE (GLIB_RCBOX_RELEASE (mem_block, 1)); + + if (clear_func != NULL) + clear_func (mem_block); + + TRACE (GLIB_RCBOX_FREE (mem_block)); + g_free (real_box); + } +} + +/** + * g_atomic_rc_box_get_size: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Retrieves the size of the reference counted data pointed by @mem_block. + * + * Returns: the size of the data, in bytes + * + * Since: 2.58 + */ +gsize +g_atomic_rc_box_get_size (gpointer mem_block) +{ + GArcBox *real_box = G_ARC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, 0); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, 0); +#endif + + return real_box->mem_size; +} diff --git a/glib/glib-autocleanups.h b/glib/glib-autocleanups.h index 9f86bd99c..ce1690b78 100644 --- a/glib/glib-autocleanups.h +++ b/glib/glib-autocleanups.h @@ -87,3 +87,4 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantDict, g_variant_dict_unref) G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GVariantDict, g_variant_dict_clear) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantType, g_variant_type_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(GStrv, g_strfreev, NULL) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GRefString, g_ref_string_release) diff --git a/glib/glib.h b/glib/glib.h index 84299c4f9..94a11fb62 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -69,7 +69,9 @@ #include #include #include +#include #include +#include #include #include #include diff --git a/glib/glib.stp.in b/glib/glib.stp.in index dc80e708d..0577d8ca2 100644 --- a/glib/glib.stp.in +++ b/glib/glib.stp.in @@ -598,3 +598,48 @@ probe glib.thread_spawned = process("@ABS_GLIB_RUNTIME_LIBDIR@/libglib-2.0.so.0. name = user_string($arg3); probestr = sprintf("glib.thread_spawned(%p, %p, %s)", func, data, name); } + +/** + * probe glib.rcbox_alloc - Called when a refcounted block is initially requested + * @mem: Raw memory pointer returned + * @n_bytes: number of bytes + * @atomic: Boolean value, %TRUE if this block is atomically refcounted + * @zeroed: Boolean value, %TRUE if this block was filled with NUL bytes + */ +probe glib.rcbox_alloc = process("@ABS_GLIB_RUNTIME_LIBDIR@/libglib-2.0.so.0.@LT_CURRENT@.@LT_REVISION@").mark("rcbox__alloc") +{ + mem = $arg1; + n_bytes = $arg2; + atomic = $arg3; + zeroed = $arg4; + probestr = sprintf("glib.rcbox_alloc(n_bytes=%d) -> %p", n_bytes, mem); +} + +/** + * probe glib.rcbox_acquire - Called when a refcounted block acquires a ref + */ +probe glib.rcbox_acquire = process("@ABS_GLIB_RUNTIME_LIBDIR@/libglib-2.0.so.0.@LT_CURRENT@.@LT_REVISION@").mark("rcbox__acquire") +{ + mem = $arg1; /* ARG: @mem: Raw memory pointer */ + atomic = $arg2; /* ARG: @atomic: Boolean value, %TRUE if the reference was acquired atomically */ + probestr = sprintf("glib.rcbox_acquire(mem=%p)", mem); +} + +/** + * probe glib.rcbox_release - Called when a refcounted block acquires a ref + */ +probe glib.rcbox_acquire = process("@ABS_GLIB_RUNTIME_LIBDIR@/libglib-2.0.so.0.@LT_CURRENT@.@LT_REVISION@").mark("rcbox__release") +{ + mem = $arg1; /* ARG: @mem: Raw memory pointer */ + atomic = $arg2; /* ARG: @atomic: Boolean value, %TRUE if the reference was released atomically */ + probestr = sprintf("glib.rcbox_release(mem=%p)", mem); +} + +/** + * probe glib.rcbox_free - Called when a refcounted block is freed + */ +probe glib.rcbox_free = process("@ABS_GLIB_RUNTIME_LIBDIR@/libglib-2.0.so.0.@LT_CURRENT@.@LT_REVISION@").mark("rcbox__free") +{ + mem = $arg1; /* ARG: @mem: Raw memory pointer */ + probestr = sprintf("glib.rcbox_free(mem=%p)", mem); +} diff --git a/glib/glib_probes.d b/glib/glib_probes.d index 29f7ff12e..d6b1f8d15 100644 --- a/glib/glib_probes.d +++ b/glib/glib_probes.d @@ -43,4 +43,8 @@ provider glib { probe source__set_name(void*, const char*); probe source__before_free(void*, void*, void*); probe thread__spawned(void*, void*, char*); + probe rcbox__alloc(void*, unsigned int, unsigned int, unsigned int); + probe rcbox__acquire(void*, unsigned int); + probe rcbox__release(void*, unsigned int); + probe rcbox__free(void*); }; diff --git a/glib/grcbox.c b/glib/grcbox.c new file mode 100644 index 000000000..204f4bc9e --- /dev/null +++ b/glib/grcbox.c @@ -0,0 +1,450 @@ +/* grcbox.c: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "config.h" + +#include "grcboxprivate.h" + +#include "gmessages.h" +#include "grefcount.h" +#include "gtestutils.h" + +#ifdef ENABLE_VALGRIND +#include "valgrind.h" +#endif + +#include "glib_trace.h" + +#include + +/** + * SECTION:rcbox + * @Title: Reference counted data + * @Short_description: Allocated memory with reference counting semantics + * + * A "reference counted box", or "RcBox", is an opaque wrapper data type + * that is guaranteed to be as big as the size of a given data type, and + * which augments the given data type with reference counting semantics + * for its memory management. + * + * RcBox is useful if you have a plain old data type, like a structure + * typically placed on the stack, and you wish to provide additional API + * to use it on the heap; or if you want to implement a new type to be + * passed around by reference without necessarily implementing copy/free + * semantics or your own reference counting. + * + * The typical use is: + * + * |[ + * typedef struct { + * char *name; + * char *address; + * char *city; + * char *state; + * int age; + * } Person; + * + * Person * + * person_new (void) + * { + * return g_rc_box_new0 (Person); + * } + * ]| + * + * Every time you wish to acquire a reference on the memory, you should + * call g_rc_box_acquire(); similarly, when you wish to release a reference + * you should call g_rc_box_release(): + * + * |[ + * // Add a Person to the Database; the Database acquires ownership + * // of the Person instance + * void + * add_person_to_database (Database *db, Person *p) + * { + * db->persons = g_list_prepend (db->persons, g_rc_box_acquire (p)); + * } + * + * // Removes a Person from the Database; the reference acquired by + * // add_person_to_database() is released here + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_rc_box_release (p); + * } + * ]| + * + * If you have additional memory allocated inside the structure, you can + * use g_rc_box_release_full(), which takes a function pointer, which + * will be called if the reference released was the last: + * + * |[ + * void + * person_clear (Person *p) + * { + * g_free (p->name); + * g_free (p->address); + * g_free (p->city); + * g_free (p->state); + * } + * + * void + * remove_person_from_database (Database *db, Person *p) + * { + * db->persons = g_list_remove (db->persons, p); + * g_rc_box_release_full (p, (GDestroyNotify) person_clear); + * } + * ]| + * + * If you wish to transfer the ownership of a reference counted data + * type without increasing the reference count, you can use g_steal_pointer(): + * + * |[ + * Person *p = g_rc_box_new (Person); + * + * // fill_person_details() is defined elsewhere + * fill_person_details (p); + * + * // add_person_to_database_no_ref() is defined elsewhere; it adds + * // a Person to the Database without taking a reference + * add_person_to_database_no_ref (db, g_steal_pointer (&p)); + * ]| + * + * ## Thread safety + * + * The reference counting operations on data allocated using g_rc_box_alloc(), + * g_rc_box_new(), and g_rc_box_dup() are not thread safe; it is your code's + * responsibility to ensure that references are acquired are released on the + * same thread. + * + * If you need thread safe reference counting, see the [atomic reference counted + * data][arcbox] API. + * + * ## Automatic pointer clean up + * + * If you want to add g_autoptr() support to your plain old data type through + * reference counting, you can use the G_DEFINE_AUTOPTR_CLEANUP_FUNC() and + * g_rc_box_release(): + * + * |[ + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, g_rc_box_release) + * ]| + * + * If you need to clear the contents of the data, you will need to use an + * ancillary function that calls g_rc_box_release_full(): + * + * |[ + * static void + * my_data_struct_release (MyDataStruct *data) + * { + * // my_data_struct_clear() is defined elsewhere + * g_rc_box_release_full (data, (GDestroyNotify) my_data_struct_clear); + * } + * + * G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyDataStruct, my_data_struct_clear) + * ]| + * + * Since: 2.58. + */ + +#define G_RC_BOX(p) (GRcBox *) (((char *) (p)) - G_RC_BOX_SIZE) + +/* We use the same alignment as GTypeInstance and GNU libc's malloc */ +#define STRUCT_ALIGNMENT (2 * sizeof (gsize)) +#define ALIGN_STRUCT(offset) ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT) + +gpointer +g_rc_box_alloc_full (gsize block_size, + gboolean atomic, + gboolean clear) +{ + /* sizeof GArcBox == sizeof GRcBox */ + gsize private_size = G_ARC_BOX_SIZE; + gsize real_size; + char *allocated; + + g_assert (block_size < (G_MAXSIZE - G_ARC_BOX_SIZE)); + real_size = private_size + block_size; + +#ifdef ENABLE_VALGRIND + if (RUNNING_ON_VALGRIND) + { + /* When running under Valgrind we massage the memory allocation + * to include a pointer at the tail end of the block; the pointer + * is then set to the start of the block. This trick allows + * Valgrind to keep track of the over-allocation and not be + * confused when passing the pointer around + */ + g_assert (private_size < (G_MAXSIZE - ALIGN_STRUCT (1))); + private_size += ALIGN_STRUCT (1); + + if (clear) + allocated = g_malloc0 (real_size + sizeof (gpointer)); + else + allocated = g_malloc (real_size + sizeof (gpointer)); + + *(gpointer *) (allocated + private_size + block_size) = allocated + ALIGN_STRUCT (1); + + VALGRIND_MALLOCLIKE_BLOCK (allocated + private_size, block_size + sizeof (gpointer), 0, TRUE); + VALGRIND_MALLOCLIKE_BLOCK (allocated + ALIGN_STRUCT (1), private_size - ALIGN_STRUCT (1), 0, TRUE); + } + else +#endif /* ENABLE_VALGRIND */ + { + if (clear) + allocated = g_malloc0 (real_size); + else + allocated = g_malloc (real_size); + } + + if (atomic) + { + GArcBox *real_box = (GArcBox *) allocated; + real_box->mem_size = block_size; +#ifndef G_DISABLE_ASSERT + real_box->magic = G_BOX_MAGIC; +#endif + g_atomic_ref_count_init (&real_box->ref_count); + } + else + { + GRcBox *real_box = (GRcBox *) allocated; + real_box->mem_size = block_size; +#ifndef G_DISABLE_ASSERT + real_box->magic = G_BOX_MAGIC; +#endif + g_ref_count_init (&real_box->ref_count); + } + + TRACE (GLIB_RCBOX_ALLOC (allocated, block_size, atomic, clear)); + + return allocated + private_size; +} + +/** + * g_rc_box_alloc: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds reference + * counting semantics to it. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_rc_box_alloc (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, FALSE, FALSE); +} + +/** + * g_rc_box_alloc0: + * @block_size: the size of the allocation, must be greater than 0 + * + * Allocates @block_size bytes of memory, and adds reference + * counting semantics to it. + * + * The contents of the returned data is set to zero. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_rc_box_alloc0 (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, FALSE, TRUE); +} + +/** + * g_rc_box_new: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate reference counted data with + * the size of the given @type. + * + * This macro calls g_rc_box_alloc() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the + * allocated memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_rc_box_new0: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate reference counted data with + * the size of the given @type, and set its contents to zero. + * + * This macro calls g_rc_box_alloc0() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * Returns: (transfer full) (not nullable): a pointer to the + * allocated memory, cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_rc_box_dup: + * @block_size: the number of bytes to copy, must be greater than 0 + * @mem_block: (not nullable): the memory to copy + * + * Allocates a new block of data with reference counting + * semantics, and copies @block_size bytes of @mem_block + * into it. + * + * Returns: (transfer full) (not nullable): a pointer to the allocated + * memory + * + * Since: 2.58 + */ +gpointer +(g_rc_box_dup) (gsize block_size, + gconstpointer mem_block) +{ + gpointer res; + + g_return_val_if_fail (block_size > 0, NULL); + g_return_val_if_fail (mem_block != NULL, NULL); + + res = g_rc_box_alloc_full (block_size, FALSE, FALSE); + memcpy (res, mem_block, block_size); + + return res; +} + +/** + * g_rc_box_acquire: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Acquires a reference on the data pointed by @mem_block. + * + * Returns: (transfer full) (not nullable): a pointer to the data, + * with its reference count increased + * + * Since: 2.58 + */ +gpointer +(g_rc_box_acquire) (gpointer mem_block) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, NULL); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, NULL); +#endif + + g_ref_count_inc (&real_box->ref_count); + + TRACE (GLIB_RCBOX_ACQUIRE (mem_block, 0)); + + return mem_block; +} + +/** + * g_rc_box_release: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * + * Releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_rc_box_release (gpointer mem_block) +{ + g_rc_box_release_full (mem_block, NULL); +} + +/** + * g_rc_box_release_full: + * @mem_block: (transfer full) (not nullable): a pointer to reference counted data + * @clear_func: (not nullable): a function to call when clearing the data + * + * Releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will call @clear_func + * to clear the contents of @mem_block, and then will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_if_fail (mem_block != NULL); +#ifndef G_DISABLE_ASSERT + g_return_if_fail (real_box->magic == G_BOX_MAGIC); +#endif + + if (g_ref_count_dec (&real_box->ref_count)) + { + TRACE (GLIB_RCBOX_RELEASE (mem_block, 0)); + + if (clear_func != NULL) + clear_func (mem_block); + + TRACE (GLIB_RCBOX_FREE (mem_block)); + g_free (real_box); + } +} + +/** + * g_rc_box_get_size: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Retrieves the size of the reference counted data pointed by @mem_block. + * + * Returns: the size of the data, in bytes + * + * Since: 2.58 + */ +gsize +g_rc_box_get_size (gpointer mem_block) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, 0); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, 0); +#endif + + return real_box->mem_size; +} diff --git a/glib/grcbox.h b/glib/grcbox.h new file mode 100644 index 000000000..23b417f09 --- /dev/null +++ b/glib/grcbox.h @@ -0,0 +1,88 @@ +/* grcbox.h: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_alloc (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_alloc0 (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_dup (gsize block_size, + gconstpointer mem_block) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_acquire (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_rc_box_release (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func); + +GLIB_AVAILABLE_IN_2_58 +gsize g_rc_box_get_size (gpointer mem_block); + +GLIB_AVAILABLE_IN_2_58 +gpointer g_atomic_rc_box_alloc (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_atomic_rc_box_alloc0 (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_atomic_rc_box_dup (gsize block_size, + gconstpointer mem_block) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_atomic_rc_box_acquire (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_atomic_rc_box_release (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_atomic_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func); + +GLIB_AVAILABLE_IN_2_58 +gsize g_atomic_rc_box_get_size (gpointer mem_block); + +#define g_rc_box_new(type) \ + ((type *) g_rc_box_alloc (sizeof (type))) +#define g_rc_box_new0(type) \ + ((type *) g_rc_box_alloc0 (sizeof (type))) +#define g_atomic_rc_box_new(type) \ + ((type *) g_atomic_rc_box_alloc (sizeof (type))) +#define g_atomic_rc_box_new0(type) \ + ((type *) g_atomic_rc_box_alloc0 (sizeof (type))) + +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && !defined(_cplusplus) +/* Type check to avoid assigning references to different types */ +# define g_rc_box_acquire(mem_block) \ + ((__typeof__(mem_block)) (g_rc_box_acquire) (mem_block)) +# define g_atomic_rc_box_acquire(mem_block) \ + ((__typeof__(mem_block)) (g_atomic_rc_box_acquire) (mem_block)) + +/* Type check to avoid duplicating data to different types */ +# define g_rc_box_dup(block_size,mem_block) \ + ((__typeof__(mem_block)) (g_rc_box_dup) (block_size,mem_block)) +# define g_atomic_rc_box_dup(block_size,mem_block) \ + ((__typeof__(mem_block)) (g_atomic_rc_box_dup) (block_size,mem_block)) +#endif + +G_END_DECLS diff --git a/glib/grcboxprivate.h b/glib/grcboxprivate.h new file mode 100644 index 000000000..8b0d8dd4e --- /dev/null +++ b/glib/grcboxprivate.h @@ -0,0 +1,61 @@ +/* grcboxprivate.h: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "gtypes.h" +#include "grcbox.h" + +G_BEGIN_DECLS + +typedef struct { + grefcount ref_count; + + gsize mem_size; + +#ifndef G_DISABLE_ASSERT + /* A "magic" number, used to perform additional integrity + * checks on the allocated data + */ + guint32 magic; +#endif +} GRcBox; + +typedef struct { + gatomicrefcount ref_count; + + gsize mem_size; + +#ifndef G_DISABLE_ASSERT + guint32 magic; +#endif +} GArcBox; + +#define G_BOX_MAGIC 0x44ae2bf0 + +/* Keep the two refcounted boxes identical in size */ +G_STATIC_ASSERT (sizeof (GRcBox) == sizeof (GArcBox)); + +#define G_RC_BOX_SIZE sizeof (GRcBox) +#define G_ARC_BOX_SIZE sizeof (GArcBox) + +gpointer g_rc_box_alloc_full (gsize block_size, + gboolean atomic, + gboolean clear); + +G_END_DECLS diff --git a/glib/grefstring.c b/glib/grefstring.c new file mode 100644 index 000000000..91fe8b5f8 --- /dev/null +++ b/glib/grefstring.c @@ -0,0 +1,270 @@ +/* grefstring.c: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +/** + * SECTION:refstring + * @Title: Reference counted strings + * @Short_description: Strings with reference counted memory management + * + * Reference counted strings are normal C strings that have been augmented + * with a reference counter to manage their resources. You allocate a new + * reference counted string and acquire and release references as needed, + * instead of copying the string among callers; when the last reference on + * the string is released, the resources allocated for it are freed. + * + * Typically, reference counted strings can be used when parsing data from + * files and storing them into data structures that are passed to various + * callers: + * + * |[ + * PersonDetails * + * person_details_from_data (const char *data) + * { + * // Use g_autoptr() to simplify error cases + * g_autoptr(GRefString) full_name = NULL; + * g_autoptr(GRefString) address = NULL; + * g_autoptr(GRefString) city = NULL; + * g_autoptr(GRefString) state = NULL; + * g_autoptr(GRefString) zip_code = NULL; + * + * // parse_person_details() is defined elsewhere; returns refcounted strings + * if (!parse_person_details (data, &full_name, &address, &city, &state, &zip_code)) + * return NULL; + * + * if (!validate_zip_code (zip_code)) + * return NULL; + * + * // add_address_to_cache() and add_full_name_to_cache() are defined + * // elsewhere; they add strings to various caches, using refcounted + * // strings to avoid copying data over and over again + * add_address_to_cache (address, city, state, zip_code); + * add_full_name_to_cache (full_name); + * + * // person_details_new() is defined elsewhere; it takes a reference + * // on each string + * PersonDetails *res = person_details_new (full_name, + * address, + * city, + * state, + * zip_code); + * + * return res; + * } + * ]| + * + * In the example above, we have multiple functions taking the same strings + * for different uses; with typical C strings, we'd have to copy the strings + * every time the life time rules of the data differ from the life time of + * the string parsed from the original buffer. With reference counted strings, + * each caller can take a reference on the data, and keep it as long as it + * needs to own the string. + * + * Reference counted strings can also be "interned" inside a global table + * owned by GLib; while an interned string has at least a reference, creating + * a new interned reference counted string with the same contents will return + * a reference to the existing string instead of creating a new reference + * counted string instance. Once the string loses its last reference, it will + * be automatically removed from the global interned strings table. + * + * Since: 2.58 + */ + +#include "config.h" + +#include "grefstring.h" + +#include "ghash.h" +#include "gmessages.h" +#include "grcbox.h" +#include "gthread.h" + +#include + +/* A global table of refcounted strings; the hash table does not own + * the strings, just a pointer to them. Strings are interned as long + * as they are alive; once their reference count drops to zero, they + * are removed from the table + */ +G_LOCK_DEFINE_STATIC (interned_ref_strings); +static GHashTable *interned_ref_strings; + +/** + * g_ref_string_new: + * @str: (not nullable): a NUL-terminated string + * + * Creates a new reference counted string and copies the contents of @str + * into it. + * + * Returns: (transfer full) (not nullable): the newly created reference counted string + * + * Since: 2.58 + */ +char * +g_ref_string_new (const char *str) +{ + char *res; + gsize len; + + g_return_val_if_fail (str != NULL, NULL); + + len = strlen (str); + + res = (char *) g_atomic_rc_box_dup (sizeof (char) * len + 1, str); + res[len] = '\0'; + + return res; +} + +/* interned_str_equal: variant of g_str_equal() that compares + * pointers as well as contents; this avoids running strcmp() + * on arbitrarily long strings, as it's more likely to have + * g_ref_string_new_intern() being called on the same refcounted + * string instance, than on a different string with the same + * contents + */ +static gboolean +interned_str_equal (gconstpointer v1, + gconstpointer v2) +{ + const char *str1 = v1; + const char *str2 = v2; + + if (v1 == v2) + return TRUE; + + return strcmp (str1, str2) == 0; +} + +/** + * g_ref_string_new_intern: + * @str: (not nullable): a NUL-terminated string + * + * Creates a new reference counted string and copies the content of @str + * into it. + * + * If you call this function multiple times with the same @str, or with + * the same contents of @str, it will return a new reference, instead of + * creating a new string. + * + * Returns: (transfer full) (not nullable): the newly created reference + * counted string, or a new reference to an existing string + * + * Since: 2.58 + */ +char * +g_ref_string_new_intern (const char *str) +{ + char *res; + + g_return_val_if_fail (str != NULL, NULL); + + G_LOCK (interned_ref_strings); + + if (G_UNLIKELY (interned_ref_strings == NULL)) + interned_ref_strings = g_hash_table_new (g_str_hash, interned_str_equal); + + res = g_hash_table_lookup (interned_ref_strings, str); + if (res != NULL) + { + /* We acquire the reference while holding the lock, to + * avoid a potential race between releasing the lock on + * the hash table and another thread releasing the reference + * on the same string + */ + g_atomic_rc_box_acquire (res); + G_UNLOCK (interned_ref_strings); + return res; + } + + res = g_ref_string_new (str); + g_hash_table_add (interned_ref_strings, res); + G_UNLOCK (interned_ref_strings); + + return res; +} + +/** + * g_ref_string_acquire: + * @str: a reference counted string + * + * Acquires a reference on a string. + * + * Returns: the given string, with its reference count increased + * + * Since: 2.58 + */ +char * +g_ref_string_acquire (char *str) +{ + g_return_val_if_fail (str != NULL, NULL); + + return g_atomic_rc_box_acquire (str); +} + +static void +remove_if_interned (gpointer data) +{ + char *str = data; + + G_LOCK (interned_ref_strings); + + if (G_LIKELY (interned_ref_strings != NULL)) + { + g_hash_table_remove (interned_ref_strings, str); + + if (g_hash_table_size (interned_ref_strings) == 0) + g_clear_pointer (&interned_ref_strings, g_hash_table_destroy); + } + + G_UNLOCK (interned_ref_strings); +} + +/** + * g_ref_string_release: + * @str: a reference counted string + * + * Releases a reference on a string; if it was the last reference, the + * resources allocated by the string are freed as well. + * + * Since: 2.58 + */ +void +g_ref_string_release (char *str) +{ + g_return_if_fail (str != NULL); + + g_atomic_rc_box_release_full (str, remove_if_interned); +} + +/** + * g_ref_string_length: + * @str: a reference counted string + * + * Retrieves the length of @str. + * + * Returns: the length of the given string, in bytes + * + * Since: 2.58 + */ +gsize +g_ref_string_length (char *str) +{ + g_return_val_if_fail (str != NULL, 0); + + return g_atomic_rc_box_get_size (str) - 1; +} diff --git a/glib/grefstring.h b/glib/grefstring.h new file mode 100644 index 000000000..2afe23a73 --- /dev/null +++ b/glib/grefstring.h @@ -0,0 +1,41 @@ +/* grefstring.h: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "gmem.h" +#include "gmacros.h" + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_new (const char *str); +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_new_intern (const char *str); + +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_acquire (char *str); +GLIB_AVAILABLE_IN_2_58 +void g_ref_string_release (char *str); + +GLIB_AVAILABLE_IN_2_58 +gsize g_ref_string_length (char *str); + +typedef char GRefString; + +G_END_DECLS diff --git a/glib/meson.build b/glib/meson.build index 76d354c2a..c05c69406 100644 --- a/glib/meson.build +++ b/glib/meson.build @@ -76,7 +76,9 @@ glib_sub_headers = files( 'gquark.h', 'gqueue.h', 'grand.h', + 'grcbox.h', 'grefcount.h', + 'grefstring.h', 'gregex.h', 'gscanner.h', 'gsequence.h', @@ -118,6 +120,7 @@ deprecated_sources = files( ) glib_sources = files( + 'garcbox.c', 'garray.c', 'gasyncqueue.c', 'gatomic.c', @@ -160,7 +163,9 @@ glib_sources = files( 'gquark.c', 'gqueue.c', 'grand.c', + 'grcbox.c', 'grefcount.c', + 'grefstring.c', 'gregex.c', 'gscanner.c', 'gsequence.c', diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am index 7289b419e..71ac15517 100644 --- a/glib/tests/Makefile.am +++ b/glib/tests/Makefile.am @@ -89,7 +89,9 @@ test_programs = \ protocol \ queue \ rand \ + rcbox \ rec-mutex \ + refstring \ regex \ rwlock \ scannerapi \ diff --git a/glib/tests/autoptr.c b/glib/tests/autoptr.c index c8c130d5c..5b3bce71c 100644 --- a/glib/tests/autoptr.c +++ b/glib/tests/autoptr.c @@ -422,6 +422,13 @@ test_strv (void) g_assert (val != NULL); } +static void +test_refstring (void) +{ + g_autoptr(GRefString) str = g_ref_string_new ("hello, world"); + g_assert_nonnull (str); +} + static void mark_freed (gpointer ptr) { @@ -539,6 +546,7 @@ main (int argc, gchar *argv[]) g_test_add_func ("/autoptr/g_variant_dict", test_g_variant_dict); g_test_add_func ("/autoptr/g_variant_type", test_g_variant_type); g_test_add_func ("/autoptr/strv", test_strv); + g_test_add_func ("/autoptr/refstring", test_refstring); g_test_add_func ("/autoptr/autolist", test_autolist); g_test_add_func ("/autoptr/autoslist", test_autoslist); diff --git a/glib/tests/meson.build b/glib/tests/meson.build index cf05bc74f..7c49d579c 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -46,9 +46,11 @@ glib_tests = [ 'protocol', 'queue', 'rand', + 'rcbox', 'rec-mutex', 'refcount', 'refcount-macro', + 'refstring', 'regex', 'rwlock', 'scannerapi', diff --git a/glib/tests/rcbox.c b/glib/tests/rcbox.c new file mode 100644 index 000000000..b1a1342bb --- /dev/null +++ b/glib/tests/rcbox.c @@ -0,0 +1,238 @@ +/* rcbox.c: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include + +typedef struct { + float x, y; +} Point; + +static Point *global_point; + +/* test_rcbox_new: Test g_rc_box_new() */ +static void +test_rcbox_new (void) +{ + Point *a = g_rc_box_new (Point); + + g_assert_nonnull (a); + g_assert_cmpuint (g_rc_box_get_size (a), ==, sizeof (Point)); + + g_rc_box_release (a); + + a = g_rc_box_new0 (Point); + g_assert_nonnull (a); + g_assert_cmpfloat (a->x, ==, 0.f); + g_assert_cmpfloat (a->y, ==, 0.f); + + g_rc_box_release (a); +} + +/* test_atomic_rcbox_new: Test g_atomic_rc_box_new() */ +static void +test_atomic_rcbox_new (void) +{ + Point *a = g_atomic_rc_box_new (Point); + + g_assert_nonnull (a); + g_assert_cmpuint (g_atomic_rc_box_get_size (a), ==, sizeof (Point)); + + g_atomic_rc_box_release (a); + + a = g_atomic_rc_box_new0 (Point); + g_assert_nonnull (a); + g_assert_cmpfloat (a->x, ==, 0.f); + g_assert_cmpfloat (a->y, ==, 0.f); + + g_atomic_rc_box_release (a); +} + +static void +point_clear (Point *p) +{ + g_assert_nonnull (p); + g_assert_true (global_point == p); + + g_assert_cmpfloat (p->x, ==, 42.0f); + g_assert_cmpfloat (p->y, ==, 47.0f); + + g_test_message ("global_point = %p", p); + global_point = NULL; +} + +/* test_rcbox_release_full: Verify that g_rc_box_release_full() calls + * the clear function only when the last reference is released + */ +static void +test_rcbox_release_full (void) +{ + Point *p = g_rc_box_new (Point); + + g_assert_nonnull (p); + global_point = p; + + p->x = 42.0f; + p->y = 47.0f; + + g_assert_true (g_rc_box_acquire (p) == p); + + g_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_nonnull (global_point); + g_assert_true (p == global_point); + + g_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_null (global_point); +} + +/* test_atomic_rcbox_release_full: Verify that g_atomic_rc_box_release_full() + * calls the clear function only when the last reference is released + */ +static void +test_atomic_rcbox_release_full (void) +{ + Point *p = g_atomic_rc_box_new (Point); + + g_assert_nonnull (p); + global_point = p; + + p->x = 42.0f; + p->y = 47.0f; + + g_assert_true (g_atomic_rc_box_acquire (p) == p); + + g_atomic_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_nonnull (global_point); + g_assert_true (p == global_point); + + g_atomic_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_null (global_point); +} + +static Point *global_point_a; +static Point *global_point_b; + +static void +point_clear_dup_a (Point *a) +{ + g_assert_true (a == global_point_a); + + g_test_message ("global_point_a = %p", a); + global_point_a = NULL; +} + +static void +point_clear_dup_b (Point *b) +{ + g_assert_true (b == global_point_b); + + g_test_message ("global_point_b = %p", b); + global_point_b = NULL; +} + +/* test_rcbox_dup: Verify that g_rc_box_dup() copies only the + * data and does not change the reference count of the original + */ +static void +test_rcbox_dup (void) +{ + Point *a, *b; + + a = g_rc_box_new (Point); + a->x = 10.f; + a->y = 5.f; + + b = g_rc_box_dup (sizeof (Point), a); + g_assert_true (a != b); + g_assert_cmpfloat (a->x, ==, b->x); + g_assert_cmpfloat (a->y, ==, b->y); + + global_point_a = a; + global_point_b = b; + + a->x = 1.f; + a->y = 1.f; + g_assert_cmpfloat (a->x, !=, b->x); + g_assert_cmpfloat (a->y, !=, b->y); + + b->x = 5.f; + b->y = 10.f; + g_assert_cmpfloat (a->x, !=, b->x); + g_assert_cmpfloat (a->y, !=, b->y); + + g_rc_box_release_full (a, (GDestroyNotify) point_clear_dup_a); + g_assert_null (global_point_a); + g_assert_nonnull (global_point_b); + + g_rc_box_release_full (b, (GDestroyNotify) point_clear_dup_b); + g_assert_null (global_point_b); +} + +/* test_atomic_rcbox_dup: Verify that g_atomic_rc_box_dup() copies + * only the data and does not change the reference count of the original + */ +static void +test_atomic_rcbox_dup (void) +{ + Point *a, *b; + + a = g_atomic_rc_box_new (Point); + a->x = 10.f; + a->y = 5.f; + + b = g_atomic_rc_box_dup (sizeof (Point), a); + g_assert_true (a != b); + g_assert_cmpfloat (a->x, ==, b->x); + g_assert_cmpfloat (a->y, ==, b->y); + + global_point_a = a; + global_point_b = b; + + a->x = 1.f; + a->y = 1.f; + g_assert_cmpfloat (a->x, !=, b->x); + g_assert_cmpfloat (a->y, !=, b->y); + + b->x = 5.f; + b->y = 10.f; + g_assert_cmpfloat (a->x, !=, b->x); + g_assert_cmpfloat (a->y, !=, b->y); + + g_atomic_rc_box_release_full (a, (GDestroyNotify) point_clear_dup_a); + g_assert_null (global_point_a); + g_assert_nonnull (global_point_b); + + g_atomic_rc_box_release_full (b, (GDestroyNotify) point_clear_dup_b); + g_assert_null (global_point_b); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/rcbox/new", test_rcbox_new); + g_test_add_func ("/rcbox/release-full", test_rcbox_release_full); + g_test_add_func ("/rcbox/dup", test_rcbox_dup); + + g_test_add_func ("/atomic-rcbox/new", test_atomic_rcbox_new); + g_test_add_func ("/atomic-rcbox/release-full", test_atomic_rcbox_release_full); + g_test_add_func ("/atomic-rcbox/dup", test_atomic_rcbox_dup); + + return g_test_run (); +} diff --git a/glib/tests/refstring.c b/glib/tests/refstring.c new file mode 100644 index 000000000..a4d8f1a8d --- /dev/null +++ b/glib/tests/refstring.c @@ -0,0 +1,83 @@ +/* refstring.c: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +/* test_refstring_base: Test the base API of GRefString */ +static void +test_refstring_base (void) +{ + char *s = g_ref_string_new ("hello, world"); + + g_test_message ("s = '%s' (%p)", s, s); + g_assert_cmpint (strcmp (s, "hello, world"), ==, 0); + g_assert_cmpint (strlen (s), ==, strlen ("hello, world")); + g_assert_cmpuint (g_ref_string_length (s), ==, strlen ("hello, world")); + + g_assert_true (g_ref_string_acquire (s) == s); + g_ref_string_release (s); + + g_ref_string_release (s); +} + +/* test_refstring_intern: Test the interning API of GRefString */ +static void +test_refstring_intern (void) +{ + char *s = g_ref_string_new_intern ("hello, world"); + char *p; + + g_test_message ("s = '%s' (%p)", s, s); + g_assert_cmpstr (s, ==, "hello, world"); + + p = g_ref_string_new_intern ("hello, world"); + g_test_message ("p = s = '%s' (%p)", p, p); + g_assert_true (s == p); + + g_test_message ("releasing p[%p] ('%s')", p, p); + g_ref_string_release (p); + + p = g_ref_string_new_intern ("goodbye, world"); + g_test_message ("p = '%s' (%p)", p, p); + g_assert_false (s == p); + + g_test_message ("releasing p[%p] ('%s')", p, p); + g_ref_string_release (p); + + g_test_message ("releasing s[%p] ('%s')", s, s); + g_ref_string_release (s); + + p = g_ref_string_new_intern ("hello, world"); + g_test_message ("p[%p] ('%s') != s[%p]", p, p, s); + g_assert_false (s == p); + g_ref_string_release (p); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/refstring/base", test_refstring_base); + g_test_add_func ("/refstring/intern", test_refstring_intern); + + return g_test_run (); +} +