[glib/refcount-box] Add atomically refcounted data



commit c2536542c0121bc9029d380460c74e34f3b35c60
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Mon Jun 11 11:52:54 2018 +0100

    Add atomically refcounted data
    
    GArcBox is the atomic reference counting version of GRcBox. Unlike
    GRcBox, the reference acquisition and release on GArcBox are guaranteed
    to be atomic, and thus they can be performed from different threads.
    
    This is similar to Rust's Arc<Box<T>> combination of traits.

 docs/reference/glib/glib-docs.xml     |   1 +
 docs/reference/glib/glib-sections.txt |  12 ++
 glib/Makefile.am                      |   2 +
 glib/garcbox.c                        | 333 ++++++++++++++++++++++++++++++++++
 glib/grcbox.c                         |  59 +++---
 glib/grcbox.h                         |  29 ++-
 glib/grcboxprivate.h                  |  42 +++++
 glib/meson.build                      |   1 +
 8 files changed, 447 insertions(+), 32 deletions(-)
---
diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml
index d7ac0a500..9d253ee5f 100644
--- a/docs/reference/glib/glib-docs.xml
+++ b/docs/reference/glib/glib-docs.xml
@@ -121,6 +121,7 @@
     <xi:include href="gvariant-text.xml"/>
     <xi:include href="xml/refcount.xml"/>
     <xi:include href="xml/rcbox.xml"/>
+    <xi:include href="xml/arcbox.xml"/>
   </chapter>
 
   <chapter id="deprecated">
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 2ed969abd..09e9d8cc6 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -3476,3 +3476,15 @@ g_rc_box_acquire
 g_rc_box_release
 g_rc_box_release_full
 </SECTION>
+
+<SECTION>
+<FILE>arcbox</FILE>
+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
+</SECTION>
diff --git a/glib/Makefile.am b/glib/Makefile.am
index fa43e3b0b..030774970 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    \
@@ -150,6 +151,7 @@ libglib_2_0_la_SOURCES =    \
        gqueue.c                \
        grand.c                 \
        grcbox.c                \
+       grcboxprivate.h         \
        grefcount.c             \
        gregex.c                \
        gscanner.c              \
diff --git a/glib/garcbox.c b/glib/garcbox.c
new file mode 100644
index 000000000..ea6e742d8
--- /dev/null
+++ b/glib/garcbox.c
@@ -0,0 +1,333 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "grcbox.h"
+
+#include "gmessages.h"
+#include "grcboxprivate.h"
+#include "grefcount.h"
+
+#ifdef ENABLE_VALGRIND
+#include "valgrind.h"
+#endif
+
+#include <string.h>
+
+#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, without necessarily implementing copy/free
+ * semantics, or your own reference counting.
+ *
+ * The typical use is:
+ *
+ * |[<!-- language="C" -->
+ * typedef struct {
+ *   float x, y;
+ * } Point;
+ *
+ * Point *
+ * point_new (float x, float y)
+ * {
+ *   Point *res = g_arc_box_new (Point);
+ *
+ *   res->x = x;
+ *   res->y = y;
+ *
+ *   return res;
+ * }
+ * ]|
+ *
+ * Every time you wish to acquire a reference on the memory, you should
+ * call g_arc_box_acquire(); similarly, when you wish to release a reference
+ * you should call g_arc_box_release():
+ *
+ * |[<!-- language="C" -->
+ * Point *
+ * point_ref (Point *p)
+ * {
+ *   return g_arc_box_acquire (p);
+ * }
+ *
+ * void
+ * point_unref (Point *p)
+ * {
+ *   g_arc_box_release (p);
+ * }
+ * ]|
+ *
+ * If you have additional memory allocated inside the structure, you can
+ * use g_arc_box_release_full(), which takes a function pointer, which
+ * will be called if the reference released was the last:
+ *
+ * |[<!-- language="C" -->
+ * typedef struct {
+ *   char *name;
+ *   char *address;
+ *   char *city;
+ *   char *state;
+ *   int age;
+ * } Person;
+ *
+ * void
+ * person_clear (Person *p)
+ * {
+ *   g_free (p->name);
+ *   g_free (p->address);
+ *   g_free (p->city);
+ *   g_free (p->state);
+ * }
+ *
+ * void
+ * person_unref (Person *p)
+ * {
+ *   g_arc_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():
+ *
+ * |[<!-- language="C" -->
+ *   Person *p = g_arc_box_new (Person);
+ *
+ *   fill_person_details (p);
+ *
+ *   add_person_to_database (db, g_steal_pointer (&p));
+ * ]|
+ *
+ * The reference counting operations on data allocated using g_arc_box_alloc(),
+ * g_arc_box_new(), and g_arc_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.
+ *
+ * Since: 2.58.
+ */
+
+/**
+ * g_arc_box_alloc:
+ * @block_size: the size of the allocation
+ *
+ * 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: a pointer to the allocated memory
+ *
+ * Since: 2.58
+ */
+gpointer
+g_arc_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_arc_box_alloc0:
+ * @block_size: the size of the allocation
+ *
+ * Allocates @block_size bytes of memory, and adds atomic
+ * referenc counting semantics to it.
+ *
+ * The contents of the returned data is set to 0's.
+ *
+ * The data will be freed when its reference count drops to
+ * zero.
+ *
+ * Returns: a pointer to the allocated memory
+ *
+ * Since: 2.58
+ */
+gpointer
+g_arc_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_arc_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_arc_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.
+ *
+ * This macro cannot return %NULL, as the minimum allocation
+ * size from `sizeof (@type)` is 1 byte.
+ *
+ * Returns: (not nullable): a pointer to the allocated memory,
+ *   cast to a pointer for the given @type
+ *
+ * Since: 2.58
+ */
+
+/**
+ * g_arc_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 0.
+ *
+ * This macro calls g_arc_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.
+ *
+ * This macro cannot return %NULL, as the minimum allocation
+ * size from `sizeof (@type)` is 1 byte.
+ *
+ * Returns: (not nullable): a pointer to the allocated memory,
+ *   cast to a pointer for the given @type
+ *
+ * Since: 2.58
+ */
+
+/**
+ * g_arc_box_dup:
+ * @mem_block: (not nullable): a pointer to reference counted data
+ *
+ * Allocates a new block of data with atomic reference counting
+ * semantics, and copies the contents of @mem_block into
+ * it.
+ *
+ * Returns: (not nullable): a pointer to the allocated memory
+ *
+ * Since: 2.58
+ */
+gpointer
+(g_arc_box_dup) (gpointer mem_block)
+{
+  GArcBox *real_box = G_ARC_BOX (mem_block);
+  gpointer res;
+
+  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
+
+  res = g_rc_box_alloc_full (real_box->mem_size, TRUE, FALSE);
+  memcpy (res, mem_block, real_box->mem_size);
+
+  return res;
+}
+
+/**
+ * g_arc_box_acquire:
+ * @mem_block: (not nullable): a pointer to reference counted data
+ *
+ * Atomically acquires a reference on the data pointed by @mem_block.
+ *
+ * Returns: (not nullabl): a pointer to the data, with its reference
+ *   count increased
+ *
+ * Since: 2.58
+ */
+gpointer
+(g_arc_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);
+
+  return mem_block;
+}
+
+/**
+ * g_arc_box_release:
+ * @mem_block: (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_arc_box_release (gpointer mem_block)
+{
+  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))
+    g_free (real_box);
+}
+
+/**
+ * g_arc_box_release_full:
+ * @mem_block: (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_arc_box_release_full (gpointer       mem_block,
+                        GDestroyNotify clear_func)
+{
+  GArcBox *real_box = G_ARC_BOX (mem_block);
+
+  g_return_if_fail (mem_block != NULL);
+  g_return_if_fail (clear_func != 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))
+    {
+      clear_func (mem_block);
+      g_free (real_box);
+    }
+}
diff --git a/glib/grcbox.c b/glib/grcbox.c
index 0860634be..d41a6def4 100644
--- a/glib/grcbox.c
+++ b/glib/grcbox.c
@@ -1,4 +1,4 @@
-/* grcbox.h: Reference counted data
+/* grcbox.c: Reference counted data
  *
  * Copyright 2018  Emmanuele Bassi
  *
@@ -21,6 +21,7 @@
 #include "grcbox.h"
 
 #include "gmessages.h"
+#include "grcboxprivate.h"
 #include "grefcount.h"
 
 #ifdef ENABLE_VALGRIND
@@ -129,35 +130,21 @@
  * Since: 2.58.
  */
 
-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;
-
-#define G_RC_BOX_MAGIC          0x44ae2bf0
-#define G_RC_BOX_SIZE           sizeof (GRcBox)
 #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)
 
-static gpointer
+gpointer
 g_rc_box_alloc_full (gsize    block_size,
+                     gboolean atomic,
                      gboolean clear)
 {
-  gsize private_size = G_RC_BOX_SIZE;
+  /* sizeof GArcBox == sizeof GRcBox */
+  gsize private_size = G_ARC_BOX_SIZE;
   gsize real_size = private_size + block_size;
   char *allocated;
-  GRcBox *real_box;
 
 #ifdef ENABLE_VALGRIND
   if (RUNNING_ON_VALGRIND)
@@ -189,12 +176,24 @@ g_rc_box_alloc_full (gsize    block_size,
         allocated = g_malloc (real_size);
     }
 
-  real_box = (GRcBox *) allocated;
-  real_box->mem_size = block_size;
+  if (atomic)
+    {
+      GArcBox *real_box = (GArcBox *) allocated;
+      real_box->mem_size = block_size;
 #ifndef G_DISABLE_ASSERT
-  real_box->magic = G_RC_BOX_MAGIC;
+      real_box->magic = G_BOX_MAGIC;
 #endif
-  g_ref_count_init (&real_box->ref_count);
+      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);
+    }
 
   return allocated + private_size;
 }
@@ -218,7 +217,7 @@ 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);
+  return g_rc_box_alloc_full (block_size, FALSE, FALSE);
 }
 
 /**
@@ -242,7 +241,7 @@ g_rc_box_alloc0 (gsize block_size)
 {
   g_return_val_if_fail (block_size > 0, NULL);
 
-  return g_rc_box_alloc_full (block_size, TRUE);
+  return g_rc_box_alloc_full (block_size, FALSE, TRUE);
 }
 
 /**
@@ -305,10 +304,10 @@ gpointer
 
   g_return_val_if_fail (mem_block != NULL, NULL);
 #ifndef G_DISABLE_ASSERT
-  g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL);
+  g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, NULL);
 #endif
 
-  res = g_rc_box_alloc_full (real_box->mem_size, FALSE);
+  res = g_rc_box_alloc_full (real_box->mem_size, FALSE, FALSE);
   memcpy (res, mem_block, real_box->mem_size);
 
   return res;
@@ -332,7 +331,7 @@ gpointer
 
   g_return_val_if_fail (mem_block != NULL, NULL);
 #ifndef G_DISABLE_ASSERT
-  g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL);
+  g_return_val_if_fail (real_box->magic == G_BOX_MAGIC, NULL);
 #endif
 
   g_ref_count_inc (&real_box->ref_count);
@@ -358,7 +357,7 @@ g_rc_box_release (gpointer mem_block)
 
   g_return_if_fail (mem_block != NULL);
 #ifndef G_DISABLE_ASSERT
-  g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC);
+  g_return_if_fail (real_box->magic == G_BOX_MAGIC);
 #endif
 
   if (g_ref_count_dec (&real_box->ref_count))
@@ -387,7 +386,7 @@ g_rc_box_release_full (gpointer       mem_block,
   g_return_if_fail (mem_block != NULL);
   g_return_if_fail (clear_func != NULL);
 #ifndef G_DISABLE_ASSERT
-  g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC);
+  g_return_if_fail (real_box->magic == G_BOX_MAGIC);
 #endif
 
   if (g_ref_count_dec (&real_box->ref_count))
diff --git a/glib/grcbox.h b/glib/grcbox.h
index 8cd44037a..2b38815d8 100644
--- a/glib/grcbox.h
+++ b/glib/grcbox.h
@@ -40,16 +40,41 @@ GLIB_AVAILABLE_IN_2_58
 void            g_rc_box_release_full   (gpointer        mem_block,
                                          GDestroyNotify  clear_func);
 
+GLIB_AVAILABLE_IN_2_58
+gpointer        g_arc_box_alloc         (gsize           block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
+GLIB_AVAILABLE_IN_2_58
+gpointer        g_arc_box_alloc0        (gsize           block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
+GLIB_AVAILABLE_IN_2_58
+gpointer        g_arc_box_dup           (gpointer        mem_block) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
+GLIB_AVAILABLE_IN_2_58
+gpointer        g_arc_box_acquire       (gpointer        mem_block);
+GLIB_AVAILABLE_IN_2_58
+void            g_arc_box_release       (gpointer        mem_block);
+GLIB_AVAILABLE_IN_2_58
+void            g_arc_box_release_full  (gpointer        mem_block,
+                                         GDestroyNotify  clear_func);
+
 #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_arc_box_new(type) \
+  ((type *) g_arc_box_alloc (sizeof (type)))
+#define g_arc_box_new0(type) \
+  ((type *) g_arc_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_rc_box_acquire(mem_block) \
+  ((__typeof__(mem_block)) (g_rc_box_acquire) (mem_block))
+# define g_arc_box_acquire(mem_block) \
+  ((__typeof__(mem_block)) (g_arc_box_acquire) (mem_block))
+
 /* Type check to avoid duplicating data to different types */
-# define g_rc_box_dup(mem_block) ((__typeof__(mem_block)) (g_rc_box_dup) (mem_block))
+# define g_rc_box_dup(mem_block) \
+  ((__typeof__(mem_block)) (g_rc_box_dup) (mem_block))
+# define g_arc_box_dup(mem_block) \
+  ((__typeof__(mem_block)) (g_arc_box_dup) (mem_block))
 #endif
 
 G_END_DECLS
diff --git a/glib/grcboxprivate.h b/glib/grcboxprivate.h
new file mode 100644
index 000000000..6599e4d4a
--- /dev/null
+++ b/glib/grcboxprivate.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "gtypes.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/meson.build b/glib/meson.build
index 73ab4928f..1a3f44797 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -119,6 +119,7 @@ deprecated_sources = files(
 )
 
 glib_sources = files(
+  'garcbox.c',
   'garray.c',
   'gasyncqueue.c',
   'gatomic.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]