1
votes

I've created some applications using PyGTK and thought they had been OK until I executed them in an environment with accessibility enabled (GNOME on Ubuntu and Openbox on Debian). I've found out they hang and what is more frustrating - they cause AT-SPI applications to hang.

I've created a repo with all variants of using PyGTK with threads I could think of:

  • primarythread prefix means gtk.main() works inside the primary thread (one executed first by Python),
  • secondarythread prefix means gtk.main() works inside the secondary thread,
  • multiprocessing prefix means import and gtk.main() are executed in another process.

  • gobject suffix means only gobject.threads_init() has been used,

  • gtkgdk suffix means both gobject and gtk.gdk.threads_init() have been used,
  • import suffix means "import gtk" is executed inside the new thread.

showApps.py is an example of an application using AT-SPI to list applications with accessibility enabled.

I've summarized tests in the table below (also inside the README file):

"Hang" column indicates if the GTK application has hung.
"Listed" column indicates if the application is visible in the AT-SPI
listing.

                                  |   hang   | listed |
----------------------------------+----------+--------+
primarythread_gobject.py          |   no     |  yes   |
primarythread_gtkgdk.py           |   no     |  yes   |
secondarythread_gobject_import.py |   no [1] |  yes   |
secondarythread_gobject.py        |   yes    |  hang  |
secondarythread_gtkgdk_import.py  |   no [1] |  yes   |
secondarythread_gtkgdk.py         |   yes    |  hang  |
multiprocessing_gobject.py        |   no     |  yes   |

[1] ** (secondarythread_gobject_import.py:5828): CRITICAL **:
giop_thread_request_push: assertion `tdata != NULL' failed
  -- at the application termination

When AT-SPI is marked "hang" it hangs unconditionally during applications listing.

Hanging of the PyGTK application happens for example after losing and regaining focus by its window.

Test showed that no problems occur when gtk.main() is run from the first/main Python thread. But this doesn't satisfy me as I don't like treating GUI as the main part of the application.

My questions are:

  1. Is there anything wrong with code in programs marked as secondarythread? Or is it a bug in GTK/GAIL/AT-SPI?
  2. Is there a policy that prohibits running gtk.main() outside the first/main Python thread?
1

1 Answers

0
votes

The only policy that I am aware of is that only one thread may access the GTK libraries at any time.

If you want the GUI to run in its own, secondary thread, then you need to be sure that that is the only thread that accesses GTK because GTK is not thread-safe. That's why in your secondarythread cases, it only works when GTK is imported from within the thread. If it's loaded outside of the thread, then technically the primary thread is also using it to some extent and both threads could try to access the library at the same time.. The reason your primarythread cases work is because you're correctly locking access to the GTK libraries with gtk.gdk.threads_enter() and threads_leave(). I think you could get rid of them in secondarythread_gtkgdk_import.py and it'll work, since there's only one thread that's aware of GTK having been loaded.

Now, this is speculation on my part since I don't know much about AT-SPI. Since you're already causing race conditions from your two separate threads in secondarythread_gobject.py and secondarythread_gtkgdk.py, AT-SPI may also somehow be affected by this state.

If you really want your GUI in a secondary thread, go with secondarythread_gtkgdk_import.py (possibly removing the threads_enter() and threads_leave() as unnecessary). I would recommend instead having your GUI as the primary thread and having all of your background processes be launched in child threads.


Compare the following two examples:

import threading
import time

class mythread(threading.Thread):
    def __init__(self):
        super(mythread, self).__init__()

    def run(self):
        print time.ctime()


t = mythread()
t.run()
print time.ctime()

$ python2 test.py
Mon Aug 15 23:12:41 2011
Mon Aug 15 23:12:41 2011

import threading

class mythread(threading.Thread):
    def __init__(self):
        super(mythread, self).__init__()

    def run(self):
        import time
        print time.ctime()


t = mythread()
t.run()
print time.ctime()

$ python2 test.py
Mon Aug 15 23:12:46 2011
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    print time.ctime()
NameError: name 'time' is not defined

In the first case, time is imported from the main thread and then a child process is spawned. The child process also has access to time as well, so the time is printed twice. In the second case, time is imported in the child thread. The parent thread, however, does not see this, and so the second call to time.ctime() fails.

When you load GTK from the child thread, the parent thread is ignorant of it, so you never run into problems with two threads trying to access the GTK libraries (to quote the GDK documentation: "That is, only one thread can use GTK+ at any given time.").