0

I wrote a screenshot program in GTK+-3 using the GDK library, it mostly works but I find sometimes the program crashes randomly with X11 errors. I realized that the crashes are triggered if a window is opened and closed instantly, and I was able to write the following program to reproduce the issue.

from time import sleep
from gi.repository import GLib, Gtk, Gdk

# (Open and select a window before the program starts.)
print("Open your window and focus on it.")

for i in range(5):
    print("Program starts in %d seconds" % (5 - i))
    sleep(1)

# Get an active window.
screen = Gdk.Screen.get_default()
window = Gdk.Screen.get_active_window(screen)

def loop():
    # Close the window in the middle of this callback.
    print(window.get_geometry())  # Crash
    return True

GLib.timeout_add(100, loop)
Gtk.main()

If I a open a window before loop() starts executing, and then suddenly close the window in the middle of loop(), the program crashes with a X11 error.

Gdk-ERROR **: 00:13:54.693: The program 'test.py' received an X Window System error.
This probably reflects a bug in the program.
The error was 'BadDrawable (invalid Pixmap or Window parameter)'.
  (Details: serial 175 error_code 9 request_code 14 (core protocol) minor_code 0)
  (Note to programmers: normally, X errors are reported asynchronously;
   that is, you will receive the error a while after causing it.
   To debug your program, run it with the GDK_SYNCHRONIZE environment
   variable to change this behavior. You can then get a meaningful
   backtrace from your debugger if you break on the gdk_x_error() function.)

Obviously, it crashes because the underlying X11 window behind this GDKWindow already disappears before get_geometry(). But how do I ensure that a GDKWindow is still valid? I tried using is_destroyed(), process_all_updates() and flush(), but none had any effect.


Solved: As the duplicate question showed, X is an asynchronous protocol, so it's simply impossible to ensure a window exists. Instead, what we should do is installing an exception handler to catch the errors from X after they occured. In GDK, exception handling can be implemented by gdk_x11_display_error_trap_push() (start ignoring and recording exceptions) and gdk_x11_display_error_trap_pop() (returns an error code if there was one).

The correct program is something like the following.

from time import sleep
from gi.repository import GLib, Gtk, Gdk, GdkX11

# (Open and select a window before the program starts.)
print("Open your window and focus on it.")

for i in range(5):
    print("Program starts in %d seconds" % (5 - i))
    sleep(1)

# Get an active window.
screen = Gdk.Screen.get_default()
window = Gdk.Screen.get_active_window(screen)

def loop():
    # Close the window in the middle of this callback.

    # ignore and catch all errors at this point.
    display = GdkX11.X11Display.get_default()
    GdkX11.X11Display.error_trap_push(display)

    # do something that may throw an X error
    print(window.get_geometry())

    # stop ignoring errors, return the error code if any
    error = GdkX11.X11Display.error_trap_pop(display)
    if (error == 0):
        print("previous operation succeed.")
    else:
        print("previous operation failed, error %d." % error)
        # do error recovery here

    return True


GLib.timeout_add(100, loop)
Gtk.main()

Output:

(x=0, y=0, width=952, height=545)
previous operation succeed.
(x=0, y=0, width=952, height=545)
previous operation succeed.
(x=-1013446544, y=22086, width=-1013446512, height=22086)
previous operation failed, error 9.
(x=-1013446544, y=22086, width=-1013446512, height=22086)
previous operation failed, error 9.
比尔盖子
  • 2,693
  • 5
  • 37
  • 53
  • Wouldn't do again `.get_active_window(screen)` what you want? – stovfl Mar 16 '20 at 17:12
  • @stovfl While adding another `get_active_window()` does prevent the issue from occurring, but it's only able to do so because the update rate is fast enough. Adding a screen capture `Gdk.pixbuf_get_from_window(window, 0, 0, 1, 1)`, and try opening and closing a window will trigger the issue because it takes time. I believe the ultimate problem is that there's no atomic way to ensure the window is available, so we have TOCTOU issue. Unfortunately, if there is no solution, running the screen capture in a separate process, catch SIGABRT and restart itself seems to be the only way out... – 比尔盖子 Mar 16 '20 at 17:34
  • @stovfl Okay, so now I discovered it's actually a feature, not a bug, X11 is asynchronous, so there cannot be a guarantee. I've marked my question as duplicate to [another relevant X11 question](https://stackoverflow.com/questions/44025639/how-can-i-check-in-xlib-if-window-exists), please vote to close it, thanks. – 比尔盖子 Mar 16 '20 at 17:56
  • @stovfl Yes, it's basically the same question. – 比尔盖子 Mar 16 '20 at 18:00

0 Answers0