GNOME Bugzilla – Bug 313523
Deadlock in atk-bridge/bridge.c when global event listener triggered in non-main thread holding GDK threads lock
Last modified: 2006-06-14 15:08:06 UTC
If a non-main thread is holding the GDK threads lock and triggers an atk-bridge global event listener (e.g. by invoking atk_object_set_role on an AtkObject) then atk-bridge deadlocks trying to communicate (by CORBA) with the main thread. This was first observed in gnome-translate http://www.nongnu.org/libtranslate/gnome-translate/ (the author calls eel_alert_dialog_new () while holding GDK thread lock in a non-main thread); a simplified testcase follows.
non-main threads shouldn't hold a GDK threads lock, should they? Seems like NOTABUG.
Created attachment 50724 [details] Test case (deadlock.c) Compile with: gcc -g -o deadlock deadlock.c $(pkg-config --cflags --libs libgnomeui-2.0 libgnome-2.0 gdk-2.0 gtk+-2.0 atk gthread-2.0)
Re comment 1: the GDK docs say: GTK+ is "thread aware" but not thread safe — it provides a global lock controlled by gdk_threads_enter()/gdk_threads_leave() which protects all use of GTK+. That is, only one thread can use GTK+ at any given time. ... Before calling gdk_threads_leave() from a thread other than your main thread, you probably want to call gdk_flush() to send all pending commands to the windowing system. The docs also have an example program which calls gtk+ functions from non-main threads, protected by gdk_threads_enter()/gdk_threads_leave().
Sorry, link: http://developer.gnome.org/doc/API/2.0/gdk/gdk-Threads.html
Continuing with analysis: when atk_object_set_role() is called the global event listener spi_atk_bridge_property_event_listener() is called, chaining to spi_atk_emit_eventv() (both in atk-bridge/bridge.c). spi_atk_emit_eventv() creates a SpiAccessible on the object, then later frees it with Accessibility_Accessible_unref(). Accessibility_Accessible_unref is defined to Bonobo_Unknown_unref(), which is an ORBit C stub; this calls ORBit_c_stub_invoke() -> ORBit_small_invoke_stub_n() -> ORBit_small_invoke_stub(), which queues the request and waits for a reply with giop_recv_buffer_get(), which deadlocks in a g_cond_wait() as the main thread is waiting on the GDK threads lock. Backtrace:
+ Trace 62449
Thread 2 (Thread -1239004240 (LWP 8322))
Confirmation is that if in spi_atk_emit_eventv() the Accessibility_Accessible_unref() is changed to bonobo_object_unref(), the deadlock disappears. I would suggest this as a patch, but I don't know enough about why Accessibility_Accessible_unref() was used instead of bonobo_object_unref() in the first place. (Note: another fix that works is to call Accessibility_Accessible_unref() in an idle handler.)
non-main threads probably should never use ATK, since ATK is basically GUI stuff.
But since control flow can enter ATK from any number of points (thanks to the atk-bridge global listeners), avoiding ATK in non-main threads pretty much means avoiding doing any GTK work; whereas the docs on gdk threading specifically say that GDK is designed to allow (lock-protected) multi-thread GUI work. And GTK _does_ work, except for this bug.
Ed: I don't understand what you mean when you assert that control flow can enter ATK from outside the GDK event thread via the global listeners. Those listeners should only be interacting with the GDK event thread.
Well, as well as the above atk_object_set_role() call, I'm also getting spi_atk_bridge_signal_listener() (with a GailWindow:bounds-changed), spi_atk_bridge_state_event_listener() (with a GailWindow:state-changed:showing), etc, all happening outside the main thread. (These might be happening from a recursive GTK main loop - I'll need to recompile GLib/GTK+ with symbols and without optimisations to check - but again the docs do not prohibit calling a recursive main loop in a non-main thread.)
re comment 9: control can enter ATK from almost any GTK function, since ATK holds global listeners on ATK properties, signals and events that are triggered by changes to the underlying GTK objects. GTK functions can be called by any thread that has the GDK thread lock. Thus control can enter ATK from any thread that has the GDK thread lock.
This was fixed by Michael in version 1.86 of at-spi/atk-bridge/bridge.c.
Yes, the testcase is fixed. Thanks.