From 9e5a53d576765819d1c7c233515b9f6e5d77eb61 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 17 Jan 2018 16:38:45 +0000 Subject: [PATCH 1/6] Add reference counting types We have a common pattern for reference counting in GLib, but we always implement it with ad hoc code. This is a good chance at trying to standardise the implementation and make it public, so that other code using GLib can take advantage of shared behaviour and semantics. Instead of simply taking an integer variable, we should create type aliases, to immediately distinguish the reference counting semantics of the code; we can handle mixing atomic reference counting with a non-atomic type (and vice versa) by using differently signed values for the atomic and non-atomic cases. The gatomicrefcount type is modelled on the Linux kernel refcount_t type; the grefcount type is added to let single-threaded code bases to avoid paying the price of atomic memory barriers on reference counting operations. --- docs/reference/glib/glib-docs.xml | 1 + docs/reference/glib/glib-sections.txt | 15 ++ glib/Makefile.am | 2 + glib/glib.h | 1 + glib/grefcount.c | 285 ++++++++++++++++++++++++++ glib/grefcount.h | 52 +++++ glib/gtypes.h | 3 + glib/meson.build | 2 + 8 files changed, 361 insertions(+) create mode 100644 glib/grefcount.c create mode 100644 glib/grefcount.h diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml index a0716c1727..26cdafb67b 100644 --- a/docs/reference/glib/glib-docs.xml +++ b/docs/reference/glib/glib-docs.xml @@ -119,6 +119,7 @@ + diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 0183b0898a..331d92c75f 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3449,3 +3449,18 @@ g_hostname_is_ip_address g_uuid_string_is_valid g_uuid_string_random + +
+refcount +grefcount +g_ref_count_init +g_ref_count_inc +g_ref_count_dec +g_ref_count_compare + +gatomicrefcount +g_atomic_ref_count_init +g_atomic_ref_count_inc +g_atomic_ref_count_dec +g_atomic_ref_count_compare +
diff --git a/glib/Makefile.am b/glib/Makefile.am index 0497061265..4d04e09daa 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -149,6 +149,7 @@ libglib_2_0_la_SOURCES = \ gquark.c \ gqueue.c \ grand.c \ + grefcount.c \ gregex.c \ gscanner.c \ gscripttable.h \ @@ -284,6 +285,7 @@ glibsubinclude_HEADERS = \ gquark.h \ gqueue.h \ grand.h \ + grefcount.h \ gregex.h \ gscanner.h \ gsequence.h \ diff --git a/glib/glib.h b/glib/glib.h index 4f5a7f702f..84299c4f90 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/grefcount.c b/glib/grefcount.c new file mode 100644 index 0000000000..10e35a217d --- /dev/null +++ b/glib/grefcount.c @@ -0,0 +1,285 @@ +/* grefcount.c: Reference counting + * + * 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:refcount + * @Title: Reference counting + * @Short_description: Reference counting types and functions + * + * Reference counting is a garbage collection mechanism that is based on + * assigning a counter to a data type, or any memory area; the counter is + * increased whenever a new reference to that data type is acquired, and + * decreased whenever the reference is released. Once the last reference + * is released, the resources associated to that data type are freed. + * + * GLib uses reference counting in many of its data types, and provides + * the #grefcount and #gatomicrefcount types to implement safe and atomic + * reference counting semantics in new data types. + * + * It is important to note that #grefcount and #gatomicrefcount should be + * considered completely opaque types; you should always use the provided + * API to increase and decrease the counters, and you should never check + * their content directly, or compare their content with other values. + * + * Since: 2.58 + */ + +#include "config.h" + +#include "grefcount.h" + +#include "gatomic.h" +#include "gmessages.h" + +/** + * grefcount: + * + * A type for implementing non-atomic reference count semantics. + * + * Use g_ref_count_init() to initialize it; g_ref_count_inc() to + * increase the counter, and g_ref_count_dec() to decrease it. + * + * It is safe to use #grefcount only if you're expecting to operate + * on the reference counter from a single thread. It is entirely up + * to you to ensure that all reference count changes happen in the + * same thread. + * + * See also: #gatomicrefcount + * + * Since: 2.58 + */ + +/** + * gatomicrefcount: + * + * A type for implementing atomic reference count semantics. + * + * Use g_atomic_ref_count_init() to initialize it; g_atomic_ref_count_inc() + * to increase the counter, and g_atomic_ref_count_dec() to decrease it. + * + * It is safe to use #gatomicrefcount if you're expecting to operate on the + * reference counter from multiple threads. + * + * See also: #grefcount + * + * Since: 2.58 + */ + +/** + * g_ref_count_init: + * @rc: the address of a reference count variable + * + * Initializes a reference count variable. + * + * Since: 2.58 + */ +void +g_ref_count_init (grefcount *rc) +{ + g_return_if_fail (rc != NULL); + + /* Non-atomic refcounting is implemented using the negative range + * of signed integers: + * + * G_MININT Z¯< 0 > Z⁺ G_MAXINT + * |----------------------------|----------------------------| + * + * Acquiring a reference moves us towards MININT, and releasing a + * reference moves us towards 0. + */ + *rc = -1; +} + +/** + * g_ref_count_inc: + * @rc: the address of a reference count variable + * + * Increases the reference count. + * + * Since: 2.58 + */ +void +g_ref_count_inc (grefcount *rc) +{ + grefcount rrc; + + g_return_if_fail (rc != NULL); + + rrc = *rc; + + g_return_if_fail (rrc < 0); + + /* Check for saturation */ + if (rrc == G_MININT) + { + g_critical ("Reference count %p has reached saturation", rc); + return; + } + + rrc -= 1; + + *rc = rrc; +} + +/** + * g_ref_count_dec: + * @rc: the address of a reference count variable + * + * Decreases the reference count. + * + * Returns: %TRUE if the reference count reached 0, and %FALSE otherwise + * + * Since: 2.58 + */ +gboolean +g_ref_count_dec (grefcount *rc) +{ + grefcount rrc; + + g_return_val_if_fail (rc != NULL, FALSE); + + rrc = *rc; + + g_return_val_if_fail (rrc < 0, FALSE); + + rrc += 1; + if (rrc == 0) + return TRUE; + + *rc = rrc; + + return FALSE; +} + +/** + * g_ref_count_compare: + * @rc: the address of a reference count variable + * @val: the value to compare + * + * Compares the current value of @rc with @val. + * + * Returns: %TRUE if the reference count is the same + * as the given value + * + * Since: 2.58 + */ +gboolean +g_ref_count_compare (grefcount *rc, + gint val) +{ + grefcount rrc; + + g_return_val_if_fail (rc != NULL, FALSE); + g_return_val_if_fail (val >= 0, FALSE); + + rrc = *rc; + + if (val == G_MAXINT) + return rrc == G_MININT; + + return rrc == -val; +} + +/** + * g_atomic_ref_count_init: + * @arc: the address of an atomic reference count variable + * + * Atomically initializes a reference count variable. + * + * Since: 2.58 + */ +void +g_atomic_ref_count_init (gatomicrefcount *arc) +{ + g_return_if_fail (arc != NULL); + + /* Atomic refcounting is implemented using the positive range + * of signed integers: + * + * G_MININT Z¯< 0 > Z⁺ G_MAXINT + * |----------------------------|----------------------------| + * + * Acquiring a reference moves us towards MAXINT, and releasing a + * reference moves us towards 0. + */ + g_atomic_int_set (arc, 1); +} + +/** + * g_atomic_ref_count_inc: + * @arc: the address of an atomic reference count variable + * + * Atomically increases the reference count. + * + * Since: 2.58 + */ +void +g_atomic_ref_count_inc (gatomicrefcount *arc) +{ + g_return_if_fail (arc != NULL); + g_return_if_fail (g_atomic_int_get (arc) > 0); + + if (g_atomic_int_get (arc) == G_MAXINT) + { + g_critical ("Reference count has reached saturation"); + return; + } + + g_atomic_int_inc (arc); +} + +/** + * g_atomic_ref_count_dec: + * @arc: the address of an atomic reference count variable + * + * Atomically decreases the reference count. + * + * Returns: %TRUE if the reference count reached 0, and %FALSE otherwise + * + * Since: 2.58 + */ +gboolean +g_atomic_ref_count_dec (gatomicrefcount *arc) +{ + g_return_val_if_fail (arc != NULL, FALSE); + g_return_val_if_fail (g_atomic_int_get (arc) > 0, FALSE); + + return g_atomic_int_dec_and_test (arc); +} + +/** + * g_atomic_ref_count_compare: + * @arc: the address of an atomic reference count variable + * @val: the value to compare + * + * Atomically compares the current value of @arc with @val. + * + * Returns: %TRUE if the reference count is the same + * as the given value + * + * Since: 2.58 + */ +gboolean +g_atomic_ref_count_compare (gatomicrefcount *arc, + gint val) +{ + g_return_val_if_fail (arc != NULL, FALSE); + g_return_val_if_fail (val >= 0, FALSE); + + return g_atomic_int_get (arc) == val; +} diff --git a/glib/grefcount.h b/glib/grefcount.h new file mode 100644 index 0000000000..b24c71e8cb --- /dev/null +++ b/glib/grefcount.h @@ -0,0 +1,52 @@ +/* grefcount.h: Reference counting + * + * 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 . + */ + +#ifndef __GREFCOUNT_H__ +#define __GREFCOUNT_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_58 +void g_ref_count_init (grefcount *rc); +GLIB_AVAILABLE_IN_2_58 +void g_ref_count_inc (grefcount *rc); +GLIB_AVAILABLE_IN_2_58 +gboolean g_ref_count_dec (grefcount *rc); +GLIB_AVAILABLE_IN_2_58 +gboolean g_ref_count_compare (grefcount *rc, + gint val); + +GLIB_AVAILABLE_IN_2_58 +void g_atomic_ref_count_init (gatomicrefcount *arc); +GLIB_AVAILABLE_IN_2_58 +void g_atomic_ref_count_inc (gatomicrefcount *arc); +GLIB_AVAILABLE_IN_2_58 +gboolean g_atomic_ref_count_dec (gatomicrefcount *arc); +GLIB_AVAILABLE_IN_2_58 +gboolean g_atomic_ref_count_compare (gatomicrefcount *arc, + gint val); + +G_END_DECLS + +#endif /* __GREFCOUNT_H__ */ diff --git a/glib/gtypes.h b/glib/gtypes.h index 09d9bd1456..67adb7f1f8 100644 --- a/glib/gtypes.h +++ b/glib/gtypes.h @@ -510,6 +510,9 @@ struct _GTimeVal glong tv_usec; }; +typedef gint grefcount; +typedef volatile gint gatomicrefcount; + G_END_DECLS /* We prefix variable declarations so they can diff --git a/glib/meson.build b/glib/meson.build index 036d1f4d60..76d354c2a7 100644 --- a/glib/meson.build +++ b/glib/meson.build @@ -76,6 +76,7 @@ glib_sub_headers = files( 'gquark.h', 'gqueue.h', 'grand.h', + 'grefcount.h', 'gregex.h', 'gscanner.h', 'gsequence.h', @@ -159,6 +160,7 @@ glib_sources = files( 'gquark.c', 'gqueue.c', 'grand.c', + 'grefcount.c', 'gregex.c', 'gscanner.c', 'gsequence.c', -- GitLab From 827c208cbf9cc0ef17b8c4531a40aafe1edc3f01 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 Jun 2018 11:38:40 +0100 Subject: [PATCH 2/6] Use macros for refcount types API If we're using GCC we can use __extension__ to inline the grefcount and gatomicrefcount API, and avoid the function call. These macros are only enabled if G_DISABLE_CHECKS is defined, as they remove critical warnings when the reference counters achieve saturation. --- glib/grefcount.c | 20 +++++++------- glib/grefcount.h | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/glib/grefcount.c b/glib/grefcount.c index 10e35a217d..37085316b9 100644 --- a/glib/grefcount.c +++ b/glib/grefcount.c @@ -89,7 +89,7 @@ * Since: 2.58 */ void -g_ref_count_init (grefcount *rc) +(g_ref_count_init) (grefcount *rc) { g_return_if_fail (rc != NULL); @@ -114,7 +114,7 @@ g_ref_count_init (grefcount *rc) * Since: 2.58 */ void -g_ref_count_inc (grefcount *rc) +(g_ref_count_inc) (grefcount *rc) { grefcount rrc; @@ -147,7 +147,7 @@ g_ref_count_inc (grefcount *rc) * Since: 2.58 */ gboolean -g_ref_count_dec (grefcount *rc) +(g_ref_count_dec) (grefcount *rc) { grefcount rrc; @@ -179,8 +179,8 @@ g_ref_count_dec (grefcount *rc) * Since: 2.58 */ gboolean -g_ref_count_compare (grefcount *rc, - gint val) +(g_ref_count_compare) (grefcount *rc, + gint val) { grefcount rrc; @@ -204,7 +204,7 @@ g_ref_count_compare (grefcount *rc, * Since: 2.58 */ void -g_atomic_ref_count_init (gatomicrefcount *arc) +(g_atomic_ref_count_init) (gatomicrefcount *arc) { g_return_if_fail (arc != NULL); @@ -229,7 +229,7 @@ g_atomic_ref_count_init (gatomicrefcount *arc) * Since: 2.58 */ void -g_atomic_ref_count_inc (gatomicrefcount *arc) +(g_atomic_ref_count_inc) (gatomicrefcount *arc) { g_return_if_fail (arc != NULL); g_return_if_fail (g_atomic_int_get (arc) > 0); @@ -254,7 +254,7 @@ g_atomic_ref_count_inc (gatomicrefcount *arc) * Since: 2.58 */ gboolean -g_atomic_ref_count_dec (gatomicrefcount *arc) +(g_atomic_ref_count_dec) (gatomicrefcount *arc) { g_return_val_if_fail (arc != NULL, FALSE); g_return_val_if_fail (g_atomic_int_get (arc) > 0, FALSE); @@ -275,8 +275,8 @@ g_atomic_ref_count_dec (gatomicrefcount *arc) * Since: 2.58 */ gboolean -g_atomic_ref_count_compare (gatomicrefcount *arc, - gint val) +(g_atomic_ref_count_compare) (gatomicrefcount *arc, + gint val) { g_return_val_if_fail (arc != NULL, FALSE); g_return_val_if_fail (val >= 0, FALSE); diff --git a/glib/grefcount.h b/glib/grefcount.h index b24c71e8cb..dec9a5ffb8 100644 --- a/glib/grefcount.h +++ b/glib/grefcount.h @@ -47,6 +47,76 @@ GLIB_AVAILABLE_IN_2_58 gboolean g_atomic_ref_count_compare (gatomicrefcount *arc, gint val); +/* On GCC we can use __extension__ to inline the API without using + * ancillary functions; we only do this when disabling checks, as + * it disables warnings when saturating the reference counters + */ +#if defined(__GNUC__) && defined(G_DISABLE_CHECKS) + +# define g_ref_count_init(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (grefcount)); \ + (void) (0 ? *(rc) ^ *(rc) : 1); \ + *(rc) = -1; \ + })) + +# define g_ref_count_inc(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (grefcount)); \ + (void) (0 ? *(rc) ^ *(rc) : 1); \ + if (*(rc) == G_MININT) ; else { \ + *(rc) -= 1; \ + } \ + })) + +# define g_ref_count_dec(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (grefcount)); \ + grefcount __rc = *(rc); \ + __rc += 1; \ + if (__rc == 0) ; else { \ + *(rc) = __rc; \ + } \ + (gboolean) (__rc == 0); \ + })) + +# define g_ref_count_compare(rc,val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (grefcount)); \ + (void) (0 ? *(rc) ^ (val) : 1); \ + (gboolean) (*(rc) == -(val)); \ + })) + +# define g_atomic_ref_count_init(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (gatomicrefcount)); \ + (void) (0 ? *(rc) ^ *(rc) : 1); \ + g_atomic_int_set ((rc), 1); \ + })) + +# define g_atomic_ref_count_inc(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (gatomicrefcount)); \ + (void) (0 ? *(rc) ^ *(rc) : 1); \ + (void) (g_atomic_int_get (rc) == G_MAXINT ? 0 : g_atomic_int_inc ((rc))); \ + })) + +# define g_atomic_ref_count_dec(rc) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (gatomicrefcount)); \ + (void) (0 ? *(rc) ^ *(rc) : 1); \ + g_atomic_int_dec_and_test ((rc)); \ + })) + +# define g_atomic_ref_count_compare(rc,val) \ + (G_GNUC_EXTENSION ({ \ + G_STATIC_ASSERT (sizeof *(rc) == sizeof (gatomicrefcount)); \ + (void) (0 ? *(rc) ^ (val) : 1); \ + (gboolean) (g_atomic_int_get (rc) == (val)); \ + })) + +#endif /* __GNUC__ && G_DISABLE_CHECKS */ + G_END_DECLS #endif /* __GREFCOUNT_H__ */ -- GitLab From 09e2247d3f3bf1a995dadcee9231add22418489d Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Sun, 10 Jun 2018 14:52:16 +0100 Subject: [PATCH 3/6] Add tests for refcount types Test that the API behaves as expected, especially when we get to saturation. Additionally, check that both the function and the macro versions of the API behave identically. --- glib/tests/meson.build | 15 ++- glib/tests/refcount.c | 221 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 glib/tests/refcount.c diff --git a/glib/tests/meson.build b/glib/tests/meson.build index e0619c4759..cf05bc74f4 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -47,6 +47,8 @@ glib_tests = [ 'queue', 'rand', 'rec-mutex', + 'refcount', + 'refcount-macro', 'regex', 'rwlock', 'scannerapi', @@ -108,14 +110,23 @@ slow_tests = [ foreach test_name : glib_tests deps = [libm, thread_dep, libglib_dep] + source = test_name + '.c' + c_args = test_cargs + ['-DPCRE_STATIC'] if test_name == 'regex' deps += [pcre] endif if test_name == 'gdatetime' deps += [libintl] endif - exe = executable(test_name, '@0@.c'.format(test_name), - c_args : ['-DPCRE_STATIC'] + test_cargs, + # We build the refcount test twice: one to test the function-based API, + # and the other to test the macro-based API that is used when disabling + # checks + if test_name == 'refcount-macro' + source = 'refcount.c' + c_args += ['-DG_DISABLE_CHECKS'] + endif + exe = executable(test_name, source, + c_args : c_args, dependencies : deps, install : false, ) diff --git a/glib/tests/refcount.c b/glib/tests/refcount.c new file mode 100644 index 0000000000..dfccc92c74 --- /dev/null +++ b/glib/tests/refcount.c @@ -0,0 +1,221 @@ +/* refcount.c: Tests for reference counting types + * + * 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_grefcount: test the behavior of the grefcount API */ +static void +test_grefcount (void) +{ + grefcount a, b; + + /* init(a): 1 */ + g_ref_count_init (&a); + if (g_test_verbose ()) + g_test_message ("init(a) := %d\n", (int) a); + g_assert_true (g_ref_count_compare (&a, 1)); + + /* inc(a): 2 */ + g_ref_count_inc (&a); + if (g_test_verbose ()) + g_test_message ("inc(a) := %d\n", (int) a); + g_assert_false (g_ref_count_compare (&a, 1)); + g_assert_false (g_ref_count_compare (&a, G_MAXINT)); + + /* b = a = 2 */ + b = a; + if (g_test_verbose ()) + g_test_message ("a := %d, b := %d\n", (int) a, (int) b); + + /* inc(a): 3 */ + g_ref_count_inc (&a); + if (g_test_verbose ()) + g_test_message ("inc(a) := %d\n", (int) a); + + /* dec(b) = 1 */ + if (g_test_verbose ()) + g_test_message ("dec(b) := %d + 1\n", (int) b); + g_assert_false (g_ref_count_dec (&b)); + + /* dec(a) = 2 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_false (g_ref_count_dec (&a)); + + /* dec(b) = 0 */ + if (g_test_verbose ()) + g_test_message ("dec(b) := %d + 1\n", (int) b); + g_assert_true (g_ref_count_dec (&b)); + + /* dec(a) = 1 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_false (g_ref_count_dec (&a)); + + /* dec(a) = 0 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_true (g_ref_count_dec (&a)); +} + +/* test_grefcount_saturation: Saturating a grefcount counter + * does not cause an overflow; additionally, if we're building + * with checks enabled, it'll cause a warning + */ +static void +test_grefcount_saturation (void) +{ + if (g_test_subprocess ()) + { + grefcount a; + + /* We're breaking abstraction here for convenience */ + a = G_MININT + 1; + + g_ref_count_inc (&a); + g_assert_true (a == G_MININT); + + g_ref_count_inc (&a); + g_assert_true (a == G_MININT); + + exit (0); + } + + g_test_trap_subprocess (NULL, 0, 0); + +#ifndef G_DISABLE_CHECKS + /* Ensure that we got a warning when building with checks; the + * test will fail because of the critical warning being caught + * by GTest + */ + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*saturation*"); +#else + /* With checks disabled we don't get any warning */ + g_test_trap_assert_passed (); +#endif +} + +/* test_gatomicrefcount: test the behavior of the gatomicrefcount API */ +static void +test_gatomicrefcount (void) +{ + gatomicrefcount a, b; + + /* init(a): 1 */ + g_atomic_ref_count_init (&a); + if (g_test_verbose ()) + g_test_message ("init(a) := %d\n", (int) a); + g_assert_true (g_atomic_ref_count_compare (&a, 1)); + + /* inc(a): 2 */ + g_atomic_ref_count_inc (&a); + if (g_test_verbose ()) + g_test_message ("inc(a) := %d\n", (int) a); + g_assert_false (g_atomic_ref_count_compare (&a, 1)); + g_assert_false (g_atomic_ref_count_compare (&a, G_MAXINT)); + + /* b = a = 2 */ + b = a; + if (g_test_verbose ()) + g_test_message ("a := %d, b := %d\n", (int) a, (int) b); + + /* inc(a): 3 */ + g_atomic_ref_count_inc (&a); + if (g_test_verbose ()) + g_test_message ("inc(a) := %d\n", (int) a); + + /* dec(b) = 1 */ + if (g_test_verbose ()) + g_test_message ("dec(b) := %d + 1\n", (int) b); + g_assert_false (g_atomic_ref_count_dec (&b)); + + /* dec(a) = 2 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_false (g_atomic_ref_count_dec (&a)); + + /* dec(b) = 0 */ + if (g_test_verbose ()) + g_test_message ("dec(b) := %d + 1\n", (int) b); + g_assert_true (g_atomic_ref_count_dec (&b)); + + /* dec(a) = 1 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_false (g_atomic_ref_count_dec (&a)); + + /* dec(a) = 0 */ + if (g_test_verbose ()) + g_test_message ("dec(a) := %d + 1\n", (int) a); + g_assert_true (g_atomic_ref_count_dec (&a)); +} + +/* test_grefcount_saturation: Saturating a gatomicrefcount counter + * does not cause an overflow; additionally, if we're building + * with checks enabled, it'll cause a warning + */ +static void +test_gatomicrefcount_saturation (void) +{ + if (g_test_subprocess ()) + { + gatomicrefcount a; + + /* We're breaking abstraction here for convenience */ + a = G_MAXINT - 1; + + g_atomic_ref_count_inc (&a); + g_assert_true (a == G_MAXINT); + + g_atomic_ref_count_inc (&a); + g_assert_true (a == G_MAXINT); + + exit (0); + } + + g_test_trap_subprocess (NULL, 0, 0); + +#ifndef G_DISABLE_CHECKS + /* Ensure that we got a warning when building with checks; the + * test will fail because of the critical warning being caught + * by GTest + */ + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*saturation*"); +#else + /* With checks disabled we don't get any warning */ + g_test_trap_assert_passed (); +#endif +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/refcount/grefcount", test_grefcount); + g_test_add_func ("/refcount/grefcount/saturation", test_grefcount_saturation); + + g_test_add_func ("/refcount/gatomicrefcount", test_gatomicrefcount); + g_test_add_func ("/refcount/gatomicrefcount/saturation", test_gatomicrefcount_saturation); + + return g_test_run (); +} -- GitLab From 439ee4822e9f5fe3e191ee8b38efe6cae4076787 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 17 Jan 2018 17:01:23 +0000 Subject: [PATCH 4/6] Port GArray and friends to gatomicrefcount Use the newly added API for reference counting instead of rolling our own. --- glib/garray.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/glib/garray.c b/glib/garray.c index 39c87ca885..8f908e4438 100644 --- a/glib/garray.c +++ b/glib/garray.c @@ -41,7 +41,7 @@ #include "gthread.h" #include "gmessages.h" #include "gqsort.h" - +#include "grefcount.h" /** * SECTION:arrays @@ -106,7 +106,7 @@ struct _GRealArray guint elt_size; guint zero_terminated : 1; guint clear : 1; - gint ref_count; + gatomicrefcount ref_count; GDestroyNotify clear_func; }; @@ -199,9 +199,10 @@ g_array_sized_new (gboolean zero_terminated, array->zero_terminated = (zero_terminated ? 1 : 0); array->clear = (clear ? 1 : 0); array->elt_size = elt_size; - array->ref_count = 1; array->clear_func = NULL; + g_atomic_ref_count_init (&array->ref_count); + if (array->zero_terminated || reserved_size != 0) { g_array_maybe_expand (array, reserved_size); @@ -257,7 +258,7 @@ g_array_ref (GArray *array) GRealArray *rarray = (GRealArray*) array; g_return_val_if_fail (array, NULL); - g_atomic_int_inc (&rarray->ref_count); + g_atomic_ref_count_inc (&rarray->ref_count); return array; } @@ -287,7 +288,7 @@ g_array_unref (GArray *array) GRealArray *rarray = (GRealArray*) array; g_return_if_fail (array); - if (g_atomic_int_dec_and_test (&rarray->ref_count)) + if (g_atomic_ref_count_dec (&rarray->ref_count)) array_free (rarray, FREE_SEGMENT); } @@ -346,7 +347,7 @@ g_array_free (GArray *farray, flags = (free_segment ? FREE_SEGMENT : 0); /* if others are holding a reference, preserve the wrapper but do free/return the data */ - if (!g_atomic_int_dec_and_test (&array->ref_count)) + if (!g_atomic_ref_count_dec (&array->ref_count)) flags |= PRESERVE_WRAPPER; return array_free (array, flags); @@ -882,7 +883,7 @@ struct _GRealPtrArray gpointer *pdata; guint len; guint alloc; - gint ref_count; + gatomicrefcount ref_count; GDestroyNotify element_free_func; }; @@ -936,9 +937,10 @@ g_ptr_array_sized_new (guint reserved_size) array->pdata = NULL; array->len = 0; array->alloc = 0; - array->ref_count = 1; array->element_free_func = NULL; + g_atomic_ref_count_init (&array->ref_count); + if (reserved_size != 0) g_ptr_array_maybe_expand (array, reserved_size); @@ -1041,7 +1043,7 @@ g_ptr_array_ref (GPtrArray *array) g_return_val_if_fail (array, NULL); - g_atomic_int_inc (&rarray->ref_count); + g_atomic_ref_count_inc (&rarray->ref_count); return array; } @@ -1066,7 +1068,7 @@ g_ptr_array_unref (GPtrArray *array) g_return_if_fail (array); - if (g_atomic_int_dec_and_test (&rarray->ref_count)) + if (g_atomic_ref_count_dec (&rarray->ref_count)) ptr_array_free (array, FREE_SEGMENT); } @@ -1107,7 +1109,7 @@ g_ptr_array_free (GPtrArray *array, /* if others are holding a reference, preserve the wrapper but * do free/return the data */ - if (!g_atomic_int_dec_and_test (&rarray->ref_count)) + if (!g_atomic_ref_count_dec (&rarray->ref_count)) flags |= PRESERVE_WRAPPER; return ptr_array_free (array, flags); -- GitLab From e67e4cb8496e6ba66832319ab89d288219acdca5 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 17 Jan 2018 17:01:52 +0000 Subject: [PATCH 5/6] Port GBytes to gatomicrefcount Use the newly added API for reference counting instead of rolling our own. --- glib/gbytes.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/glib/gbytes.c b/glib/gbytes.c index 3b14a51cd5..74f8148f60 100644 --- a/glib/gbytes.c +++ b/glib/gbytes.c @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -69,7 +70,7 @@ struct _GBytes { gconstpointer data; /* may be NULL iff (size == 0) */ gsize size; /* may be 0 */ - gint ref_count; + gatomicrefcount ref_count; GDestroyNotify free_func; gpointer user_data; }; @@ -187,7 +188,7 @@ g_bytes_new_with_free_func (gconstpointer data, bytes->size = size; bytes->free_func = free_func; bytes->user_data = user_data; - bytes->ref_count = 1; + g_atomic_ref_count_init (&bytes->ref_count); return (GBytes *)bytes; } @@ -310,7 +311,7 @@ g_bytes_ref (GBytes *bytes) { g_return_val_if_fail (bytes != NULL, NULL); - g_atomic_int_inc (&bytes->ref_count); + g_atomic_ref_count_inc (&bytes->ref_count); return bytes; } @@ -330,7 +331,7 @@ g_bytes_unref (GBytes *bytes) if (bytes == NULL) return; - if (g_atomic_int_dec_and_test (&bytes->ref_count)) + if (g_atomic_ref_count_dec (&bytes->ref_count)) { if (bytes->free_func != NULL) bytes->free_func (bytes->user_data); @@ -438,7 +439,7 @@ try_steal_and_unref (GBytes *bytes, return NULL; /* Are we the only reference? */ - if (g_atomic_int_get (&bytes->ref_count) == 1) + if (g_atomic_ref_count_compare (&bytes->ref_count, 1)) { *size = bytes->size; result = (gpointer)bytes->data; -- GitLab From 927de4433ef2c66c54bbf0ad9b7fa2c1c366b9ea Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 17 Jan 2018 17:02:25 +0000 Subject: [PATCH 6/6] Port GHashTable to gatomicrefcount Use the newly added API for reference counting instead of rolling our own. --- glib/ghash.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/glib/ghash.c b/glib/ghash.c index 87218114fc..d931690666 100644 --- a/glib/ghash.c +++ b/glib/ghash.c @@ -37,7 +37,7 @@ #include "gatomic.h" #include "gtestutils.h" #include "gslice.h" - +#include "grefcount.h" /** * SECTION:hash_tables @@ -227,7 +227,7 @@ struct _GHashTable GHashFunc hash_func; GEqualFunc key_equal_func; - gint ref_count; + gatomicrefcount ref_count; #ifndef G_DISABLE_ASSERT /* * Tracks the structure of the hash table, not its contents: is only @@ -374,7 +374,7 @@ g_hash_table_lookup_node (GHashTable *hash_table, * (as keys, etc. will be NULL). * Applications need to either use g_hash_table_destroy, or ensure the hash * table is empty prior to removing the last reference using g_hash_table_unref(). */ - g_assert (hash_table->ref_count > 0); + g_assert (!g_atomic_ref_count_compare (&hash_table->ref_count, 0)); hash_value = hash_table->hash_func (key); if (G_UNLIKELY (!HASH_IS_REAL (hash_value))) @@ -716,11 +716,11 @@ g_hash_table_new_full (GHashFunc hash_func, hash_table = g_slice_new (GHashTable); g_hash_table_set_shift (hash_table, HASH_TABLE_MIN_SHIFT); + g_atomic_ref_count_init (&hash_table->ref_count); hash_table->nnodes = 0; hash_table->noccupied = 0; hash_table->hash_func = hash_func ? hash_func : g_direct_hash; hash_table->key_equal_func = key_equal_func; - hash_table->ref_count = 1; #ifndef G_DISABLE_ASSERT hash_table->version = 0; #endif @@ -1077,7 +1077,7 @@ g_hash_table_ref (GHashTable *hash_table) { g_return_val_if_fail (hash_table != NULL, NULL); - g_atomic_int_inc (&hash_table->ref_count); + g_atomic_ref_count_inc (&hash_table->ref_count); return hash_table; } @@ -1098,7 +1098,7 @@ g_hash_table_unref (GHashTable *hash_table) { g_return_if_fail (hash_table != NULL); - if (g_atomic_int_dec_and_test (&hash_table->ref_count)) + if (g_atomic_ref_count_dec (&hash_table->ref_count)) { g_hash_table_remove_all_nodes (hash_table, TRUE, TRUE); if (hash_table->keys != hash_table->values) -- GitLab