After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 699909 - Do not use toggle_refs in CamelObjectBag
Do not use toggle_refs in CamelObjectBag
Status: RESOLVED FIXED
Product: evolution
Classification: Applications
Component: Mailer
3.8.x (obsolete)
Other Linux
: Normal normal
: ---
Assigned To: evolution-mail-maintainers
Evolution QA team
Depends on:
Blocks:
 
 
Reported: 2013-05-08 09:07 UTC by David Woodhouse
Modified: 2013-07-01 09:00 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Attempted patch (9.31 KB, patch)
2013-05-08 21:48 UTC, David Woodhouse
none Details | Review
New attempt (9.55 KB, patch)
2013-05-08 22:34 UTC, David Woodhouse
none Details | Review
Use g_object_weak_ref too, to avoid potential memory leak (10.43 KB, patch)
2013-05-09 08:46 UTC, David Woodhouse
needs-work Details | Review
Patch #4 (10.46 KB, patch)
2013-05-10 16:03 UTC, David Woodhouse
committed Details | Review

Description David Woodhouse 2013-05-08 09:07:06 UTC
GLib-GObject:ERROR:gobject.c:2732:toggle_refs_notify: assertion failed: (tstack.n_toggle_refs == 1)

Program received signal SIGABRT, Aborted.
0x0000003909235819 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56	  return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
(gdb) imap(dwmw2): Info: Disconnected for inactivity in=1874 out=3740

(gdb) bt
  • #0 __GI_raise
    at ../nptl/sysdeps/unix/sysv/linux/raise.c line 56
  • #1 __GI_abort
    at abort.c line 90
  • #2 g_assertion_message
    at gtestutils.c line 1912
  • #3 g_assertion_message_expr
    at gtestutils.c line 1923
  • #4 toggle_refs_notify
    at gobject.c line 2732
  • #5 g_object_ref
    at gobject.c line 2896
  • #6 g_weak_ref_get
    at gobject.c line 4067
  • #7 folder_emit_changed_cb
    at camel-folder.c line 187
  • #8 g_main_dispatch
    at gmain.c line 3054
  • #9 g_main_context_dispatch
    at gmain.c line 3630
  • #10 g_main_context_iterate
    at gmain.c line 3701
  • #11 g_main_loop_run
    at gmain.c line 3895
  • #12 gtk_main
    at gtkmain.c line 1156
  • #13 main
    at main.c line 707

Comment 1 David Woodhouse 2013-05-08 09:09:03 UTC
(gdb) t a a bt

Thread 9219 (Thread 0x7fff1d1e4700 (LWP 17423))

  • #0 malloc_consolidate
    at malloc.c line 4093
  • #1 _int_malloc
    at malloc.c line 3364
  • #2 __GI___libc_malloc
    at malloc.c line 2863
  • #3 sqlite3MemMalloc
    at sqlite3.c line 15581
  • #4 mallocWithAlarm
    at sqlite3.c line 18879
  • #5 sqlite3Malloc
    at sqlite3.c line 18912
  • #6 sqlite3ParserAlloc
    at sqlite3.c line 110012
  • #7 sqlite3RunParser
    at sqlite3.c line 47281
  • #8 sqlite3Prepare
    at sqlite3.c line 94461
  • #9 sqlite3LockAndPrepare
    at sqlite3.c line 94553
  • #10 sqlite3_prepare
    at sqlite3.c line 94617
  • #11 sqlite3_exec
    at sqlite3.c line 90822
  • #12 cdb_sql_exec
    at camel-db.c line 455
  • #13 camel_db_select
    at camel-db.c line 1029
  • #14 camel_db_read_folder_info_record
    at camel-db.c line 1871
  • #15 camel_folder_summary_header_load_from_db
    at camel-folder-summary.c line 2913
  • #16 camel_folder_summary_load_from_db
    at camel-folder-summary.c line 2410
  • #17 camel_eas_summary_new
    at camel-eas-summary.c line 182
  • #18 camel_eas_folder_new
    at camel-eas-folder.c line 525
  • #19 eas_get_folder_sync
    at camel-eas-store.c line 322
  • #20 camel_store_get_folder_sync
  • #21 e_mail_session_uri_to_folder_sync
  • #22 refresh_folders_exec
    at mail-send-recv.c line 1044
  • #23 mail_msg_proxy
    at mail-mt.c line 426
  • #24 g_thread_pool_thread_proxy
    at gthreadpool.c line 309
  • #25 g_thread_proxy
    at gthread.c line 798
  • #26 start_thread
    at pthread_create.c line 308
  • #27 clone
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S line 113

Thread 1 (Thread 0x7ffff7f86a40 (LWP 28597))

  • #0 __GI_raise
    at ../nptl/sysdeps/unix/sysv/linux/raise.c line 56
  • #1 __GI_abort
    at abort.c line 90
  • #2 g_assertion_message
    at gtestutils.c line 1912
  • #3 g_assertion_message_expr
    at gtestutils.c line 1923
  • #4 toggle_refs_notify
    at gobject.c line 2732
  • #5 g_object_ref
    at gobject.c line 2896
  • #6 g_weak_ref_get
    at gobject.c line 4067
  • #7 folder_emit_changed_cb
    at camel-folder.c line 187
  • #8 g_main_dispatch
    at gmain.c line 3054
  • #9 g_main_context_dispatch
    at gmain.c line 3630
  • #10 g_main_context_iterate
    at gmain.c line 3701
  • #11 g_main_loop_run
    at gmain.c line 3895
  • #12 gtk_main
    at gtkmain.c line 1156
  • #13 main
    at main.c line 707

Comment 2 David Woodhouse 2013-05-08 09:14:59 UTC
(gdb) p *(GObject *)0x1baa150
$4 = {g_type_instance = {g_class = 0x7fffa8002c80}, ref_count = 1, qdata = 0x7fff28003990}

(gdb) p *(GObjectClass *)0x7fffa8002c80
$5 = {g_type_class = {g_type = 140736011971584}, construct_properties = 0x7fffb00ac060 = {0x127bc00, 0x1296030, 0xaf9350, 0xaf92e0, 0x7fffe0003ab0}, constructor = 0x390c614580 <g_object_constructor>, set_property = 0x0, get_property = 0x0, dispose = 0x7fffe90d4dc0 <eas_folder_dispose>, finalize = 0x7fffe90d4d60 <eas_folder_finalize>, dispatch_properties_changed = 0x390c613d60 <g_object_dispatch_properties_changed>, notify = 0x0, constructed = 0x7fffe90d4320 <eas_folder_constructed>, flags = 1, pdummy = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}

Hm, mea culpa? 

Thread 9219 is in the middle of creating the folder in question, I presume. Is there a race condition here?
Comment 3 David Woodhouse 2013-05-08 09:34:30 UTC
It looks like the folder creation (in particular the database operation for the summary creation) is taking longer than normal due — this box does have performance issues. And somehow, camel_folder_changed() is getting called before we're finished. And the folder_emit_changed_cb() is happening when we don't expect it? I don't quite understand the refcounting here, but I don't think the ActiveSync back end is doing anything special. It's fairly much the same as the EWS back end that I copied it from.

(gdb) p *(CamelEasFolder *)0x1baa150
$6 = {parent = {parent = {parent = {parent = {g_type_instance = {g_class = 0x7fffa8002c80}, ref_count = 1, qdata = 0x7fff28003990}, priv = 0x1baa1d0}, priv = 0x1baa1e0, summary = 0x47d3330 [CamelEasSummary], folder_flags = CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY, permanent_flags = 4127, later = {0x0, 0x0, 0x0, 0x0}}, priv = 0x1baa240}, priv = 0x1baa250, search = 0x7fff140185c0 [CamelFolderSearch], cache = 0x7fff9000e0e0 [CamelDataCache]}
(gdb) p *(CamelEasSummary *)0x47d3330
$7 = {parent = {parent = {parent = {g_type_instance = {g_class = 0x7fffc4004a60}, ref_count = 1, qdata = 0x0}, priv = 0x47d33b0}, priv = 0x47d33c0, version = 14, time = 0, flags = (unknown: 0), collate = 0x0, sort_by = 0x0, later = {0x0, 0x0, 0x0, 0x0}}, sync_state = 0x7fff29d84040 "1986612094", version = 1}
Comment 4 David Woodhouse 2013-05-08 16:08:11 UTC
Looking at the code in toggle_refs_notify() I don't really understand how it can be correct. It looks like this:

  G_UNLOCK (toggle_refs_mutex);

  /* Reentrancy here is not as tricky as it seems, because a toggle reference
   * will only be notified when there is exactly one of them.
   */
  g_assert (tstack.n_toggle_refs == 1);
  tstack.toggle_refs[0].notify (tstack.toggle_refs[0].data, tstack.object, is_last_ref);
}


Note: It *drops* toggle_refs_mutex. Then it can sleep for an unspecified period of time, while g_object_add_toggle_ref() or g_object_remove_toggle_ref() can be called. And then it hits the assert(), with absolutely no justification for beliving that n_toggle_refs will *still* be precisely one, even if that was the case when the function was called.
Comment 5 David Woodhouse 2013-05-08 20:23:25 UTC
It looks like toggle_ref is not intended to be used for this purpose. It isn't entirely thread-safe.

This usage of toggle_ref was added in commit 3ee0ee8a in order to fix bug 638810, and a glib RFE (bug 667424) was filed to give us a better way of coping with it.

That glib RFE has been responded to, with "see GWeakRef". That is probably the answer.
Comment 6 David Woodhouse 2013-05-08 21:48:57 UTC
Created attachment 243627 [details] [review]
Attempted patch

Completely untested. Does compile.
Comment 7 Carl-Anton Ingmarsson 2013-05-08 22:18:39 UTC
Review of attachment 243627 [details] [review]:

::: camel/camel-object-bag.c
@@ +348,3 @@
+			/* We have an object; no need to reserve the key. */
+			object_bag_unreserve (bag, key);
+			object = g_weak_ref_get (&ref->ref);

You already did g_weak_ref_get() above

@@ +415,3 @@
 		gpointer copied_key;
 
+		ref = g_new(ObjRef, 1);

Perhaps g_slice_new() should be used instead
Comment 8 David Woodhouse 2013-05-08 22:34:16 UTC
Created attachment 243638 [details] [review]
New attempt

New patch incorporating the above feedback (thanks) and also results from actual testing. Incremental patch below for illustration:

--- a/camel/camel-object-bag.c.gob2	2013-05-08 23:22:06.903789921 +0100
+++ a/camel/camel-object-bag.c	2013-05-08 23:29:37.030229134 +0100
@@ -120,7 +120,7 @@ wref_free_func (gpointer p)
 	ObjRef *ref = p;
 
 	g_weak_ref_clear (&ref->ref);
-	g_free (ref);
+	g_slice_free (ObjRef, ref);
 }
 
 /**
@@ -347,7 +347,6 @@ camel_object_bag_reserve (CamelObjectBag
 		if (object != NULL) {
 			/* We have an object; no need to reserve the key. */
 			object_bag_unreserve (bag, key);
-			object = g_weak_ref_get (&ref->ref);
 		}
 
 		/* Remove stale reference to dead object. */
@@ -414,7 +413,7 @@ camel_object_bag_add (CamelObjectBag *ba
 		ObjRef *ref;
 		gpointer copied_key;
 
-		ref = g_new(ObjRef, 1);
+		ref = g_slice_new(ObjRef);
 		/* We need to stash a 'raw' pointer since that's the key we use
 		   in the key_table */
 		ref->obj = object;
@@ -523,7 +522,9 @@ camel_object_bag_list (CamelObjectBag *b
 
 	values = g_hash_table_get_values (bag->object_table);
 	while (values != NULL) {
-		g_ptr_array_add (array, g_object_ref (values->data));
+		GObject *obj = g_weak_ref_get (values->data);
+		if (obj)
+			g_ptr_array_add (array, obj);
 		values = g_list_delete_link (values, values);
 	}
Comment 9 David Woodhouse 2013-05-09 08:46:24 UTC
Created attachment 243677 [details] [review]
Use g_object_weak_ref too, to avoid potential memory leak

I was concerned that an approach based purely on GWeakRef might leave the "stale" GWeakRef corresponding to dead objects around for ever, which is effectively a memory leak. It's all very well removing them "on demand" when we try to look them up or add something in their place, but that might not happen.

So this version adds the g_object_weak_ref() callback again, while preserving the safety that's provided by using GWeakRef.

Incremental patch from before:

--- a/camel/camel-object-bag.c	2013-05-08 23:29:37.030229134 +0100
+++ a/camel/camel-object-bag.c	2013-05-09 09:42:59.959862625 +0100
@@ -111,14 +111,41 @@
 typedef struct {
 	GWeakRef ref;
 	gconstpointer *obj;
+	CamelObjectBag *bag;
 } ObjRef;
 
-/* Properly destroy a GWeakRef as it's freed from the hash table */
+static void
+object_bag_notify (CamelObjectBag *bag,
+		   GObject *where_the_object_was)
+{
+        gconstpointer key;
+
+	g_mutex_lock (&bag->mutex);
+
+	key = g_hash_table_lookup (bag->key_table, where_the_object_was);
+	if (key != NULL) {
+		g_hash_table_remove (bag->key_table, where_the_object_was);
+		g_hash_table_remove (bag->object_table, key);
+	}
+
+	g_mutex_unlock (&bag->mutex);
+}
+
+/* Properly destroy an ObjRef as it's freed from the hash table */
 static void
 wref_free_func (gpointer p)
 {
 	ObjRef *ref = p;
+	GObject *obj = g_weak_ref_get (&ref->ref);
 
+	if (obj) {
+		/* The object is being removed from the bag while it's
+		   still alive, e.g. by camel_object_bag_remove()
+		   or camel_object_bag_destroy(). Drop the weak_ref. */
+		g_object_weak_unref (obj, (GWeakNotify)object_bag_notify,
+				     ref->bag);
+		g_object_unref (obj);
+	}
 	g_weak_ref_clear (&ref->ref);
 	g_slice_free (ObjRef, ref);
 }
@@ -414,6 +441,7 @@
 		gpointer copied_key;
 
 		ref = g_slice_new(ObjRef);
+		ref->bag = bag;
 		/* We need to stash a 'raw' pointer since that's the key we use
 		   in the key_table */
 		ref->obj = object;
@@ -422,6 +450,9 @@
 		g_hash_table_insert (bag->key_table, object, copied_key);
 		g_hash_table_insert (bag->object_table, copied_key, ref);
 		object_bag_unreserve (bag, key);
+
+		g_object_weak_ref (G_OBJECT (object),
+				   (GWeakNotify) object_bag_notify, bag);
 	}
 
 	g_mutex_unlock (&bag->mutex);
@@ -522,7 +553,8 @@
 
 	values = g_hash_table_get_values (bag->object_table);
 	while (values != NULL) {
-		GObject *obj = g_weak_ref_get (values->data);
+		ObjRef *ref = values->data;
+		GObject *obj = g_weak_ref_get (&ref->ref);
 		if (obj)
 			g_ptr_array_add (array, obj);
 		values = g_list_delete_link (values, values);
Comment 10 Milan Crha 2013-05-10 15:45:45 UTC
Review of attachment 243677 [details] [review]:

I'd like to do thorough testing with all the below addressed (including coding style issues not mentioned here) :)

::: a/camel/camel-object-bag.c.gob
@@ +111,3 @@
+typedef struct {
+	GWeakRef ref;
+	gconstpointer *obj;

it should be 'gpointer obj;' instead

@@ +119,3 @@
+		   GObject *where_the_object_was)
+{
+        gconstpointer key;

here are used spaces, instead of tab

@@ +143,3 @@
+		   still alive, e.g. by camel_object_bag_remove()
+		   or camel_object_bag_destroy(). Drop the weak_ref. */
+		g_object_weak_unref (obj, (GWeakNotify)object_bag_notify,

missing space after the cast; There are plenty of other similar coding style issues

@@ +259,3 @@
+		if (object != NULL) {
+			g_mutex_unlock (&bag->mutex);
+			return object;

by this you keep a reservation in the bag on the object, thus the return should not be here

@@ +377,3 @@
+		}
+
+		/* Remove stale reference to dead object. */

it's not necessarily dead here (should be in 'else' body)
Comment 11 David Woodhouse 2013-05-10 16:03:38 UTC
Created attachment 243802 [details] [review]
Patch #4

Thanks for the feedback.
Comment 12 Milan Crha 2013-05-10 16:42:30 UTC
Review of attachment 243802 [details] [review]:

Thanks for the fix. Just fix the below and commit, to master only. I would keep this in master only, for better testing. You said you got it only once, thus it's a big race condition anyway.

::: camel/camel-object-bag.c
@@ +111,3 @@
+typedef struct {
+	GWeakRef ref;
+	gpointer *obj;

I didn't mean only gconstpointer replace, I meant that a gpointer is a 'void *' already, thus you get void **obj, which is not what you want.
Comment 13 David Woodhouse 2013-05-10 20:17:27 UTC
Committed; thanks.

https://git.gnome.org/browse/evolution-data-server/commit/?id=87b365d9

(Ick, this is why I hate gratuitous typedefs like gpointer. If it's a pointer, it should have a * on it.)
Comment 14 Milan Crha 2013-07-01 09:00:16 UTC
David, can be a downstream bug report [1] related to your changes? It crashed on place where it didn't crash for years, and the 3.8.3 contains the above fix.

[1] https://bugzilla.redhat.com/show_bug.cgi?id=979689