Как проверить, действительно ли GDKWindow, полученное ранее, все еще действует в GTK+-3?

Я написал программу для создания снимков экрана в GTK+-3 с использованием библиотеки GDK, в основном она работает, но иногда я обнаруживаю, что программа случайным образом вылетает с ошибками X11. Я понял, что сбои возникают, если окно открывается и закрывается мгновенно, и я смог написать следующую программу, чтобы воспроизвести проблему.

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()

Если я открою окно до начала выполнения loop(), а затем внезапно закрою окно в середине loop(), программа вылетит с ошибкой X11.

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.)

Очевидно, он падает, потому что базовое окно X11 за этим GDKWindow уже исчезает до get_geometry(). Но как мне убедиться, что GDKWindow по-прежнему действителен? Я пробовал использовать is_destroyed(), process_all_updates() и flush(), но ни один из них не дал никакого эффекта.


Решено: как показал повторяющийся вопрос, X является асинхронным протоколом, поэтому просто невозможно гарантировать, что окно существует. Вместо этого мы должны установить обработчик исключений, чтобы перехватывать ошибки из X после их возникновения. В GDK обработка исключений может быть реализована с помощью gdk_x11_display_error_trap_push() (начать игнорировать и записывать исключения) и gdk_x11_display_error_trap_pop() (возвращать код ошибки, если она была).

Правильная программа выглядит примерно так.

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()

Выход:

(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.

person 比尔盖子    schedule 16.03.2020    source источник
comment
Не стал бы снова делать .get_active_window(screen) то, что ты хочешь?   -  person stovfl    schedule 16.03.2020
comment
@stovfl Добавление еще одного get_active_window() действительно предотвращает возникновение проблемы, но это возможно только потому, что скорость обновления достаточно высока. Добавление снимка экрана Gdk.pixbuf_get_from_window(window, 0, 0, 1, 1) и попытка открыть и закрыть окно вызовет проблему, потому что это требует времени. Я считаю, что конечная проблема заключается в том, что нет атомарного способа обеспечить доступность окна, поэтому у нас есть проблема с TOCTOU. К сожалению, если нет решения, запуск захвата экрана в отдельном процессе, перехват SIGABRT и перезапуск самого себя кажется единственным выходом...   -  person 比尔盖子    schedule 16.03.2020
comment
@stovfl Хорошо, теперь я обнаружил, что это на самом деле функция, а не ошибка, X11 асинхронный, поэтому гарантии быть не может. Я пометил свой вопрос как дубликат для другого соответствующего вопроса X11, пожалуйста, проголосуйте, чтобы закрыть его, спасибо.   -  person 比尔盖子    schedule 16.03.2020
comment
@stovfl Да, это в основном тот же вопрос.   -  person 比尔盖子    schedule 16.03.2020