From 8b03077a44092ce2b510ef3246da063cacc8d951 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Thu, 14 Apr 2011 09:54:17 -0400 Subject: [PATCH] GTimeZone: fix non-threadsafe refcounting In the previous code, if the timezone was pulled out of the cache again just as the last reference was being dropped, the cache code will increase its refcount and return it while the unref code was freeing it. Protect against that. Closes #646435. --- glib/gtimezone.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/glib/gtimezone.c b/glib/gtimezone.c index 2f77f018e..0d9d6f742 100644 --- a/glib/gtimezone.c +++ b/glib/gtimezone.c @@ -146,29 +146,41 @@ static GHashTable/**/ *time_zones; void g_time_zone_unref (GTimeZone *tz) { - g_assert (tz->ref_count > 0); + int ref_count; - if (g_atomic_int_dec_and_test (&tz->ref_count)) +again: + ref_count = g_atomic_int_get (&tz->ref_count); + + g_assert (ref_count > 0); + + if (ref_count == 1) { if G_UNLIKELY (tz == local_timezone) { g_critical ("The last reference on the local timezone was just " "dropped, but GTimeZone itself still owns one. This " "means that g_time_zone_unref() was called too many " - "times. Restoring the refcount to 1."); + "times. Returning without lowering the refcount."); /* We don't want to just inc this back again since if there * are refcounting bugs in the code then maybe we are already * at -1 and inc will just take us back to 0. Set to 1 to be * sure. */ - tz->ref_count = 1; return; } if (tz->name != NULL) { G_LOCK(time_zones); + + /* someone else might have grabbed a ref in the meantime */ + if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1) + { + G_UNLOCK(time_zones); + goto again; + } + g_hash_table_remove (time_zones, tz->name); G_UNLOCK(time_zones); } @@ -180,6 +192,11 @@ g_time_zone_unref (GTimeZone *tz) g_slice_free (GTimeZone, tz); } + + else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count, + ref_count, + ref_count - 1)) + goto again; } /**