Camel string pool improvements.
[platform/upstream/evolution-data-server.git] / camel / camel-string-utils.c
index 417a9d6..0089fe5 100644 (file)
@@ -141,172 +141,216 @@ gchar camel_toupper (gchar c)
 }
 
 /* working stuff for pstrings */
-static GStaticMutex pstring_lock = G_STATIC_MUTEX_INIT;
-static GHashTable *pstring_table = NULL;
+static GStaticMutex string_pool_lock = G_STATIC_MUTEX_INIT;
+static GHashTable *string_pool = NULL;
+
+typedef struct _StringPoolNode StringPoolNode;
+
+struct _StringPoolNode {
+       gchar *string;
+       gulong ref_count;
+};
+
+static StringPoolNode *
+string_pool_node_new (gchar *string)
+{
+       StringPoolNode *node;
+
+       node = g_slice_new (StringPoolNode);
+       node->string = string;  /* takes ownership */
+       node->ref_count = 1;
+
+       return node;
+}
+
+static void
+string_pool_node_free (StringPoolNode *node)
+{
+       g_free (node->string);
+
+       g_slice_free (StringPoolNode, node);
+}
+
+static guint
+string_pool_node_hash (const StringPoolNode *node)
+{
+       return g_str_hash (node->string);
+}
+
+static gboolean
+string_pool_node_equal (const StringPoolNode *node_a,
+                        const StringPoolNode *node_b)
+{
+       return g_str_equal (node_a->string, node_b->string);
+}
+
+static void
+string_pool_init (void)
+{
+       if (G_UNLIKELY (string_pool == NULL))
+               string_pool = g_hash_table_new_full (
+                       (GHashFunc) string_pool_node_hash,
+                       (GEqualFunc) string_pool_node_equal,
+                       (GDestroyNotify) string_pool_node_free,
+                       (GDestroyNotify) NULL);
+}
 
 /**
  * camel_pstring_add:
- * @str: string to add to the string pool
- * @own: whether the string pool will own the memory pointed to by @str, if @str is not yet in the pool
+ * @string: string to add to the string pool
+ * @own: whether the string pool will own the memory pointed to by
+ *       @string, if @string is not yet in the pool
+ *
+ * Add @string to the pool.
  *
- * Add the string to the pool.
+ * The %NULL and empty strings are special cased to constant values.
  *
- * The NULL and empty strings are special cased to constant values.
+ * Unreference the returned string with camel_pstring_free().
  *
- * Returns: A pointer to an equivalent string of @s.  Use
- * camel_pstring_free() when it is no longer needed.
+ * Returns: a canonicalized copy of @string
  **/
 const gchar *
-camel_pstring_add (gchar *str,
+camel_pstring_add (gchar *string,
                    gboolean own)
 {
-       gpointer pcount;
-       gpointer pstr;
+       StringPoolNode static_node = { string, };
+       StringPoolNode *node;
+       const gchar *interned;
 
-       if (str == NULL)
+       if (string == NULL)
                return NULL;
 
-       if (str[0] == '\0') {
+       if (*string == '\0') {
                if (own)
-                       g_free (str);
+                       g_free (string);
                return "";
        }
 
-       g_static_mutex_lock (&pstring_lock);
-       if (pstring_table == NULL)
-               pstring_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+       g_static_mutex_lock (&string_pool_lock);
+
+       string_pool_init ();
 
-       if (g_hash_table_lookup_extended (pstring_table, str, &pstr, &pcount)) {
-               gint *count = pcount;
-               *count = (*count) + 1;
+       node = g_hash_table_lookup (string_pool, &static_node);
+
+       if (node != NULL) {
+               node->ref_count++;
                if (own)
-                       g_free (str);
+                       g_free (string);
        } else {
-               gint *count = g_new0 (gint, 1);
-               *count = 1;
-               pstr = own ? str : g_strdup (str);
-               g_hash_table_insert (pstring_table, pstr, count);
+               if (!own)
+                       string = g_strdup (string);
+               node = string_pool_node_new (string);
+               g_hash_table_add (string_pool, node);
        }
 
-       g_static_mutex_unlock (&pstring_lock);
+       interned = node->string;
+
+       g_static_mutex_unlock (&string_pool_lock);
 
-       return pstr;
+       return interned;
 }
 
 /**
  * camel_pstring_peek:
- * @str: string to fetch to the string pool
+ * @string: string to fetch from the string pool
  *
- * Add return the string from the pool.
+ * Returns the canonicalized copy of @string without increasing its
+ * reference count in the string pool.  If necessary, @string is first
+ * added to the string pool.
  *
- * The NULL and empty strings are special cased to constant values.
+ * The %NULL and empty strings are special cased to constant values.
  *
- * Returns: A pointer to an equivalent string of @s.  Use
- * camel_pstring_free() when it is no longer needed.
+ * Returns: a canonicalized copy of @string
  *
  * Since: 2.24
  **/
 const gchar *
-camel_pstring_peek (const gchar *str)
+camel_pstring_peek (const gchar *string)
 {
-       gpointer pcount;
-       gpointer pstr;
+       StringPoolNode static_node = { (gchar *) string, };
+       StringPoolNode *node;
+       const gchar *interned;
 
-       if (str == NULL)
+       if (string == NULL)
                return NULL;
 
-       if (str[0] == '\0') {
+       if (*string == '\0')
                return "";
-       }
 
-       g_static_mutex_lock (&pstring_lock);
-       if (pstring_table == NULL)
-               pstring_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+       g_static_mutex_lock (&string_pool_lock);
 
-       if (!g_hash_table_lookup_extended (pstring_table, str, &pstr, &pcount)) {
-               gint *count = g_new0 (gint, 1);
-               *count = 1;
-               pstr = g_strdup (str);
-               g_hash_table_insert (pstring_table, pstr, count);
+       string_pool_init ();
+
+       node = g_hash_table_lookup (string_pool, &static_node);
+
+       if (node == NULL) {
+               node = string_pool_node_new (g_strdup (string));
+               g_hash_table_add (string_pool, node);
        }
 
-       g_static_mutex_unlock (&pstring_lock);
+       interned = node->string;
+
+       g_static_mutex_unlock (&string_pool_lock);
 
-       return pstr;
+       return interned;
 }
+
 /**
  * camel_pstring_strdup:
- * @s: String to copy.
+ * @string: string to copy
+ *
+ * Create a new pooled string entry for @strings.  A pooled string
+ * is a table where common strings are canonicalized.  They are also
+ * reference counted and freed when no longer referenced.
  *
- * Create a new pooled string entry for the string @s.  A pooled
- * string is a table where common strings are uniquified to the same
- * pointer value.  They are also refcounted, so freed when no longer
- * in use.  In a thread-safe manner.
+ * The %NULL and empty strings are special cased to constant values.
  *
- * The NULL and empty strings are special cased to constant values.
+ * Unreference the returned string with camel_pstring_free().
  *
- * Returns: A pointer to an equivalent string of @s.  Use
- * camel_pstring_free() when it is no longer needed.
+ * Returns: a canonicalized copy of @string
  **/
 const gchar *
-camel_pstring_strdup (const gchar *s)
+camel_pstring_strdup (const gchar *string)
 {
-       return camel_pstring_add ((gchar *) s, FALSE);
+       return camel_pstring_add ((gchar *) string, FALSE);
 }
 
 /**
  * camel_pstring_free:
- * @s: String to free.
- *
- * De-ref a pooled string. If no more refs exist to this string, it will be deallocated.
+ * @string: string to free
  *
- * NULL and the empty string are special cased.
+ * Unreferences a pooled string.  If the string's reference count drops to
+ * zero it will be deallocated.  %NULL and the empty string are special cased.
  **/
 void
-camel_pstring_free (const gchar *s)
+camel_pstring_free (const gchar *string)
 {
-       gpointer pstr;
-       gpointer pcount;
+       StringPoolNode static_node = { (gchar *) string, };
+       StringPoolNode *node;
 
-       if (pstring_table == NULL)
+       if (string_pool == NULL)
                return;
-       if (s == NULL || s[0] == 0)
+
+       if (string == NULL || *string == '\0')
                return;
 
-       g_static_mutex_lock (&pstring_lock);
-       if (g_hash_table_lookup_extended (pstring_table, s, &pstr, &pcount)) {
-               gint *count = pcount;
-               *count = (*count) - 1;
-               if ((*count) == 0) {
-                       g_hash_table_remove (pstring_table, pstr);
-                       g_free (pstr);
-                       if (g_getenv("CDS_DEBUG")) {
-                               if (pstr != s) /* Only for debugging purposes */
-                                       g_assert (0);
-                       }
-               }
-       } else {
-               if (g_getenv("CDS_DEBUG")) {
-                       g_warning("Trying to free string not allocated from the pool '%s'", s);
-                       /*Only for debugging purposes */
-                       g_assert (0);
-               }
-       }
-       g_static_mutex_unlock (&pstring_lock);
-}
+       g_static_mutex_lock (&string_pool_lock);
 
-static void
-count_pstring_memory_cb (gpointer key,
-                         gpointer value,
-                         gpointer user_data)
-{
-       const gchar *str = key;
-       guint64 *pbytes = user_data;
+       node = g_hash_table_lookup (string_pool, &static_node);
 
-       g_return_if_fail (str != NULL);
-       g_return_if_fail (pbytes != NULL);
+       if (node == NULL) {
+               g_warning ("%s: String not in pool: %s", G_STRFUNC, string);
+       } else if (node->string != string) {
+               g_warning ("%s: String is not ours: %s", G_STRFUNC, string);
+       } else if (node->ref_count == 0) {
+               g_warning ("%s: Orphaned pool node: %s", G_STRFUNC, string);
+       } else {
+               node->ref_count--;
+               if (node->ref_count == 0)
+                       g_hash_table_remove (string_pool, node);
+       }
 
-       *pbytes += strlen (str);
+       g_static_mutex_unlock (&string_pool_lock);
 }
 
 /**
@@ -317,16 +361,33 @@ count_pstring_memory_cb (gpointer key,
 void
 camel_pstring_dump_stat (void)
 {
-       g_static_mutex_lock (&pstring_lock);
+       g_static_mutex_lock (&string_pool_lock);
+
+       g_print ("   String Pool Statistics: ");
 
-       if (!pstring_table) {
-               g_print ("   String Pool Statistics: Not used yet\n");
+       if (string_pool == NULL) {
+               g_print ("Not used yet\n");
        } else {
+               GHashTableIter iter;
+               gchar *format_size;
                guint64 bytes = 0;
+               gpointer key;
+
+               g_hash_table_iter_init (&iter, string_pool);
+
+               while (g_hash_table_iter_next (&iter, &key, NULL))
+                       bytes += strlen (((StringPoolNode *) key)->string);
+
+               format_size = g_format_size_full (
+                       bytes, G_FORMAT_SIZE_LONG_FORMAT);
+
+               g_print (
+                       "Holds %d strings totaling %s\n",
+                       g_hash_table_size (string_pool),
+                       format_size);
 
-               g_hash_table_foreach (pstring_table, count_pstring_memory_cb, &bytes);
-               g_print ("   String Pool Statistics: Holds %d strings of total %" G_GUINT64_FORMAT " bytes\n", g_hash_table_size (pstring_table), bytes);
+               g_free (format_size);
        }
 
-       g_static_mutex_unlock (&pstring_lock);
+       g_static_mutex_unlock (&string_pool_lock);
 }