_XReply() завершает приложение с _XIOError()

Мы разрабатываем сложное приложение, состоящее из бинарного linux, интегрированного с вызовами java jni (из JVM, созданного в бинарном linux) из нашего собственного файла .jar. Вся работа с графическим интерфейсом реализована и выполняется частью Java. Каждый раз, когда необходимо изменить какое-либо свойство графического интерфейса или перерисовать графический интерфейс, это делается с помощью вызова jni к JVM.

Полный дисплей/графический интерфейс перерисовывается (или обновляется) настолько быстро, насколько это может обработать JVM/java. Это делается итеративно и часто, несколько сотен или тысяч итераций в секунду.

Через какое-то точное время приложение завершается с exit(1), который я поймал с помощью gdb для вызова из _XIOError(). Это прекращение может быть повторено через более или менее точный период времени, например. примерно через 15 часов на двухъядерном процессоре x86 2,5 ГГц. Если я использую более медленный компьютер, он работает дольше, как будто он пропорционален скорости процессора/графического процессора. Можно сделать вывод, что в какой-то части xorg закончился какой-то ресурс или что-то в этом роде.

Вот моя обратная связь:

#0  0xb7fe1424 in __kernel_vsyscall ()
#1  0xb7c50941 in raise () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#2  0xb7c53d72 in abort () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#3  0xb7fdc69d in exit () from /temp/bin/liboverrides.so
#4  0xa0005c80 in _XIOError () from /usr/lib/i386-linux-gnu/libX11.so.6
#5  0xa0003afe in _XReply () from /usr/lib/i386-linux-gnu/libX11.so.6
#6  0x9fffee7b in XSync () from /usr/lib/i386-linux-gnu/libX11.so.6
#7  0xa01232b8 in X11SD_GetSharedImage () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt_xawt.so
#8  0xa012529e in X11SD_GetRasInfo () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt_xawt.so
#9  0xa01aac3d in Java_sun_java2d_loops_ScaledBlit_Scale () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt.so

Я сделал свой собственный вызов exit() в liboverrides.so и использовал его с LD_PRELOAD для захвата вызова exit() в gdb с помощью abort()/SIGABRT. После некоторой отладки libX11 и libxcb я заметил, что _XReply() получил ответ NULL (ответ от xcb_wait_for_reply()), который вызывает вызов _XIOError() и exit(1). Углубившись в libxcb в функции xcb_wait_for_reply(), я заметил, что одна из причин, по которой он может возвращать ответ NULL, заключается в том, что он обнаруживает сломанное или закрытое соединение сокета, что может быть моей ситуацией.

В целях тестирования, если я изменю xcb_io.c и проигнорирую _XIOError(), приложение больше не будет работать. И если я повторяю запрос внутри _XReply(), он каждый раз терпит неудачу, т.е. получает ответ NULL на каждый xcb_wait_for_reply().

Итак, мои вопросы будут заключаться в том, почему произошло такое неконтролируемое завершение приложения с выходом (1) из _XReply() -> XIOError() -> exit(1), или как я могу узнать причину, почему и что произошло, чтобы я мог это исправить или найти обходной путь.

Чтобы эта проблема повторилась, как я писал выше, мне нужно подождать около 15 часов, но в настоящее время у меня очень мало времени для отладки и я не могу найти причину проблемы/прекращения. Мы также попытались реорганизовать java-часть, которая отвечает за обновление графического интерфейса/дисплея, но проблема не была решена.

Некоторые факты о ПО:
- java jre 1.8.0_20, даже с java 7 проблема может повториться
- libX11.so 1.5.0
- libxcb.so 1.8.1
- debian wheezy
- ядро ​​3.2.0


person dudo    schedule 26.05.2014    source источник


Ответы (1)


Вероятно, это известная проблема в libX11, связанная с обработкой номеров запросов, используемых для xcb_wait_for_reply.

В какой-то момент после того, как libxcb v1.5 был введен код для внутреннего использования 64-битных порядковых номеров везде, и была добавлена ​​логика для расширения порядковых номеров при входе в те общедоступные API, которые все еще принимают 32-битные порядковые номера.

Вот цитата из отправленного отчета об ошибке libxcb (фактические электронные письма удалены):

У нас есть приложение, которое делает много XDrawString и XDrawLine. Через несколько часов приложение закрывается с ошибкой XIOError.

XIOError вызывается в libX11 в файле xcb_io.c, функция _XReply. Он не получил ответа от xcb_wait_for_reply.

С libxcb 1.5 все в порядке, с libxcb 1.8.1 — нет. Разделение libxcb пополам указывает на этот коммит:

commit ed37b087519ecb9e74412e4df8f8a217ab6d12a9 Автор: Джейми Шарп Дата: суббота, 9 октября, 17:13:45 2010 -0700

xcb_in: Use 64-bit sequence numbers internally everywhere.

Widen sequence numbers on entry to those public APIs that still take
32-bit sequence numbers.

Signed-off-by: Jamey Sharp <[email protected]>

Откат поверх 1.8.1 помогает.

Добавляя трассировки в libxcb, я обнаружил, что последние номера запросов, используемые для xcb_wait_for_reply, таковы: 4294900463 и 4294965487 (два вызова в цикле while функции _XReply), через полсекунды: 63215 (затем вызывается XIOError). Расширение_запроса также равно 63215, я ожидал 63215+2^32. Поэтому кажется, что запрос неправильно расширен.

Коммит выше также изменил сравнения в poll_for_reply с XCB_SEQUENCE_COMPARE_32 на XCB_SEQUENCE_COMPARE. Возможно, расширение никогда не работало корректно, но оно никогда не наблюдалось, потому что сравнивались только младшие 32 бита.

Воспроизведение проблемы

Вот исходный фрагмент кода из отправленного отчета об ошибке, который использовался для воспроизведения проблемы:

  for(;;) {
    XDrawLine(dpy, w, gc, 10, 60, 180, 20);
    XFlush(dpy);
  }

и, по-видимому, проблему можно воспроизвести с помощью еще более простого кода:

 for(;;) {
    XNoOp(dpy);
  }

Согласно представленному отчету об ошибке libxcb, эти условия необходимы для воспроизведения (при условии, что код воспроизведения находится в xdraw.c):

  • libxcb >= 1.8 (т.е. включает коммит ed37b08)
  • скомпилировано с 32-битной версией: gcc -m32 -lX11 -o xdraw xdraw.c
  • счетчик последовательности сбрасывается.

Предлагаемый патч

Предлагаемый патч, который можно применить поверх libxcb 1.8.1, выглядит следующим образом:

diff --git a/src/xcb_io.c b/src/xcb_io.c
index 300ef57..8616dce 100644
--- a/src/xcb_io.c
+++ b/src/xcb_io.c
@@ -454,7 +454,7 @@ void _XSend(Display *dpy, const char *data, long size)
        static const xReq dummy_request;
        static char const pad[3];
        struct iovec vec[3];
-       uint64_t requests;
+       unsigned long requests;
        _XExtension *ext;
        xcb_connection_t *c = dpy->xcb->connection;
        if(dpy->flags & XlibDisplayIOError)
@@ -470,7 +470,7 @@ void _XSend(Display *dpy, const char *data, long size)
        if(dpy->xcb->event_owner != XlibOwnsEventQueue || dpy->async_handlers)
        {
                uint64_t sequence;
-               for(sequence = dpy->xcb->last_flushed + 1; sequence <= dpy->request; ++sequence)
+               for(sequence = dpy->xcb->last_flushed + 1; (unsigned long) sequence <= dpy->request; ++sequence)
                        append_pending_request(dpy, sequence);
        }
        requests = dpy->request - dpy->xcb->last_flushed;

Подробное техническое объяснение

Ниже вы найдете подробное техническое объяснение Джонаса Петерсена ( также включено в вышеупомянутый отчет об ошибке):

Hi,

Вот два патча. Первый исправляет ошибку переноса 32-битной последовательности. Второй патч лишь добавляет комментарий к другому соответствующему утверждению.

Патчи содержат некоторые детали. Вот вся история, кому может быть интересно:

Xlib (libx11) приведет к сбою приложения с «Фатальной ошибкой ввода-вывода 11 (ресурс временно недоступен)» после 4 294 967 296 запросов к серверу. Это происходит, когда внутренняя 32-битная последовательность Xlib завершается.

Большинство приложений, вероятно, вряд ли достигнут этого числа, но если они это сделают, у них есть шанс умереть загадочной смертью. Например, приложение, над которым я работаю, всегда аварийно завершало работу примерно через 20 часов, когда я начинал проводить стресс-тестирование. Он интенсивно рисует через Xlib, используя gktmm2, pixmaps и gc, рисуя со скоростью 40 кадров в секунду в разрешении Full HD (в Ubuntu). Некоторые оптимизации действительно увеличили отсрочку примерно до 35 часов, но все равно произошел сбой.

Затем последовали несколько разочаровывающих недель копания и отладки, чтобы понять, что это не в моем приложении, не в gtkmm, gtk или glib, а в том, что это небольшая ошибка в Xlib, которая, по-видимому, существует с 2006-10-06.

Потребовалось некоторое время, чтобы выяснить, что число 0x100000000 (2^32) имеет некоторое значение. (Намного) позже выяснилось, что это можно воспроизвести только с помощью Xlib, используя, например, этот код:

while(1) { XDrawPoint(display, drawable, gc, x, y); XFlush(отображение); }

Это может занять один или два часа, но когда он достигнет 4294 миллионов, произойдет «Фатальная ошибка ввода-вывода 11».

Затем я узнал, что, хотя Xlib использует внутренние 32-битные порядковые номера, они (разумно) расширяются до 64-битных в процессе, так что 32-битная последовательность может переноситься без каких-либо нарушений в расширенную 64-битную последовательность. Очевидно, с этим должно быть что-то не так.

Фатальная ошибка ввода-вывода выдается в _XReply(), когда она не получает ответа там, где он должен быть, но причина кроется в _XSend() раньше, в момент переноса 32-битного порядкового номера Xlib.

Проблема в том, что когда он переносится на 0, значение last_flushed все еще будет на верхней границе (например, 0xffffffff). В _XSend() (xcb_io.c) есть два места, которые терпят неудачу в этом состоянии, потому что они полагаются на то, что эти значения все время последовательны, первое место:

запросы = dpy->запрос - dpy->xcb->last_flushed;

В случае запроса = 0x0 и last_flushed = 0xffffffff он назначит 0xffffffff00000001 «запросам», а затем XCB как количество (количество) запросов. Это главный убийца.

Вторая локация такова:

for(последовательность = dpy->xcb->last_flush + 1; последовательность ‹= dpy->запрос; \++последовательность)

В случае запроса = 0x0 (меньше, чем last_flushed) нет возможности когда-либо войти в цикл, и в результате некоторые запросы игнорируются.

Решение состоит в том, чтобы «развернуть» dpy->request в этих двух местах и, таким образом, сохранить последовательность, связанную с last_flushed.

uint64_t unwrapped_request = ((uint64_t)(dpy->запрос ‹ \ dpy->xcb->last_flush) ‹‹ 32) + dpy->запрос;

Он создает временный 64-битный номер запроса, в котором бит 8 установлен, если «request» меньше, чем «last_flushed». Затем он используется в двух местах вместо dpy->request.

Я не уверен, что было бы более эффективно использовать этот оператор вместо использования переменной.

В require_socket() есть еще одна строка, которая поначалу меня беспокоила:

dpy->xcb->last_flushed = dpy->request = отправлено;

Это 64-битное, 32-битное, 64-битное назначение. Он усекает «отправлено» до 32-битного при присвоении его «запросу», а затем также присваивает усеченное значение (64-битному) «last_flushed». Но, похоже, заинтересован. Я добавил примечание, объясняющее, что для следующих проблем с последовательностью отладки бедняги... :-)

  • Йонас

Йонас Петерсен (2): xcb_io: исправлен перенос номера 32-битного запроса Xlib xcb_io: добавлен комментарий, объясняющий двойное присвоение смешанного типа

источник/xcb_io.c | 14 +++++++++++ --- 1 файл изменен, 11 вставок(+), 3 удаления(-)

-- 1.7.10.4

Удачи!

person mzagar    schedule 26.05.2014