[glib] GTimeZone: fix non-threadsafe refcounting



commit 8b03077a44092ce2b510ef3246da063cacc8d951
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Apr 14 09:54:17 2011 -0400

    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 files changed, 21 insertions(+), 4 deletions(-)
---
diff --git a/glib/gtimezone.c b/glib/gtimezone.c
index 2f77f01..0d9d6f7 100644
--- a/glib/gtimezone.c
+++ b/glib/gtimezone.c
@@ -146,29 +146,41 @@ static GHashTable/*<string?, GTimeZone>*/ *time_zones;
 void
 g_time_zone_unref (GTimeZone *tz)
 {
-  g_assert (tz->ref_count > 0);
+  int ref_count;
+
+again:
+  ref_count = g_atomic_int_get (&tz->ref_count);
 
-  if (g_atomic_int_dec_and_test (&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;
 }
 
 /**



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