Access Violation on qt5 backend, talk about it with AI

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Ответить
Аватара пользователя
sunjob
постоялец
Сообщения: 184
Зарегистрирован: 12.01.2011 14:19:54

Access Violation on qt5 backend, talk about it with AI

Сообщение sunjob »

день добрый!

исходные:

Код: Выделить всё

- slackware-14.2
- gcc-5.3.0
- glibc-2.23
- binutils-2.45.0
- oprofile-1.4.0
- fpc-3.2.2
- lazarus-3.8.0 gtk/qt4/qt5
разбираю чужой код. в глаза бросается излишнее(imho) использование параметра AutoSize для TMemo, установленные в инспеторе (указываю это, т.к. считаю "причастным")

изначально было "заблокировано" изменение размеров формы (тем или иным спопобом).
по ходу разборок/тестовых запусков - замечается а-периодическое вываливание исключения:
- только в qt5 бекенде
- когда пытаюсь "схватить и изменить размеры" формы

поварил котелком, так-сяк, в разных режимах штурма, не помогло.
обратился к "очень интересному и авторитетному" собеседнику.
диалог получился интересным. хотелось бы услышать мнения/идеи по теме этих и подобный "исключений".
далее стенограмма (очевидные, не нужные вещи убраны).

////////////////////////////////////////////////////////////////////////////////

случайным образом вываливает access violation и в консоль сообщение:

Код: Выделить всё

qt.qpa.xcb: QXcbConnection: XCB error: 3 (BadWindow), sequence: 2315, resource id: 16837823, major code: 40 (TranslateCoords), minor code: 0
qt.qpa.xcb: QXcbConnection: XCB error: 3 (BadWindow), sequence: 3003, resource id: 16838147, major code: 40 (TranslateCoords), minor code: 0
-->

Не обращай внимания. Это не Access Violation, а шум от Qt.

Сообщения вида

Код: Выделить всё

QXcbConnection: XCB error: 3 (BadWindow) 
известная проблема в Qt5 под Linux, абсолютно безвредная

# Что происходит

- Ошибка BadWindow (3) возникает, когда X-сервер посылает событие мыши на окно, которое уже было закрыто между отправкой и обработкой события
- Это штатная ситуация в асинхронной среде X11.
- Множество приложений (kwin_x11, kdevelop, kate, qtcreator) регулярно печатают эти предупреждения

# Что делать?

Ничего. Если программа работает нормально, а Access Violation не повторяется постоянно - это просто диагностика Qt.

Если хочешь убрать шум из консоли:
- Запускай программу с перенаправлением stderr:

Код: Выделить всё

./prj 2>/dev/null
- Или настрой переменную окружения:

Код: Выделить всё

QT_LOGGING_RULES="qt.qpa.xcb.warning=false" ./prj
Если Access Violation реальный и повторяется всегда - проблема в другом месте.

+

вываливает исключиение с соответствующим messagebox, в котором предлагают закрыть/продолжить
когда происходит исключение - в консоль выхлоп с текстом

-->

Это не просто консольный шум, а реальное исключение с MessageBox.
Значит Qt здесь не при чем - это LCL под Linux ловит X11 ошибку и превращает её в исключение.

# Где копать?

Ошибка BadWindow(3) с major code 40 (TranslateCoords) возникает когда:
1. Окно или контрол был удален (например, Form.Close + Free)
2. Курсор мыши находится над удаленным окном в момент перемещения
3. Application.ProcessMessages вызывается в неподходящий момент

# Решение

1. Проверь удаление форм

Код: Выделить всё

Form.Close;    // НЕПРАВИЛЬНО: только прячет, не удаляет
Form.Release;  // ПРАВИЛЬНО  : или Form.Free, но Release безопаснее
2. Защити ProcessMessages

Код: Выделить всё

// Если вызываешь ProcessMessages в цикле
if not Application.Terminated then
  Application.ProcessMessages;

3. Отключи X ошибки в LCL / Можно попробовать:

Код: Выделить всё

uses
  LCLIntf;
  
// В начале программы
SetExceptionMask(GetExceptionMask + [exInvalidOp, exOverflow, exUnderflow]);
4. Временный фикс
Если исключение появляется редко, самый простой способ - добавить обработчик:

Код: Выделить всё

Application.OnException := @CustomExceptionHandler;
+

еще немного поразмышляй на эту тему
-->

Раз у тебя вылетает не просто консольное предупреждение, а исключение с MessageBox, значит LCL превращает эту ошибку X-сервера в исключение.

# Почему это происходит?

Судя по логам, ошибка BadWindow (3) с major code 40 (TranslateCoords) возникает, когда X-сервер пытается отправить событие (например, движение мыши или координаты) окну, которое уже было уничтожено

Это классическая "гонка" в асинхронной среде X11:
- Пользователь кликает/закрывает окно
- LCL инициирует удаление окна
- X-сервер еще "думает", что окно существует, и пытается отправить ему событие
- Возникает ошибка, и LCL превращает её в исключение

# Ключевой момент:
В Qt это считается безвредным и было понижено с Warning до Debug
А в LCL, видимо, это все еще вызывает исключение.

# Где конкретно искать виновника:

1. Application.ProcessMessages в неожиданных местах
Если в цикле или обработчике вызывается ProcessMessages, а в этот момент окно уже начали закрывать - это частая причина.

2. Освобождение окон в неправильном порядке
Проверь, не удаляются ли окна/контролы в обработчиках событий других окон. Особенно если одно окно вызывает Free другого.

3. Проблемы с очередью сообщений при shutdown системы
На форуме Lazarus обсуждают похожую проблему: при выключении Linux одно из дочерних окон получает "левые" сигналы закрытия раньше главного, что приводит к странным состояниям

# Что попробовать:

1. Обернуть все удаления форм в try..except

Код: Выделить всё

try
  Form2.Free;
except
  on E: Exception do
  Log('Ошибка при удалении формы: ' + E.Message);
end;
2. Использовать Release вместо Free

Release планирует удаление после завершения текущего цикла обработки событий:

Код: Выделить всё

Form.Release;  // безопаснее, чем Free
3. Отловить глобальное исключение и проглотить это конкретное

Код: Выделить всё

Application.OnException := @HandleException;

procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
  if Pos('BadWindow', E.Message) > 0 then
    // Игнорируем эту ошибку
  else
    Application.ShowException(E);
end;
4. Проверить наличие таймеров или асинхронных вызовов

Если есть TTimer или Application.QueueAsyncCall, которые работают с формой после её закрытия.

# Главный вопрос:
Исключение всегда вылетает в одном и том же месте?
Или случайно?
Если случайно - скорее всего гонка, и без глобального перехвата не обойтись.

+

уточнение:
- тестируются 3 бекэнда (gtk/qt4/qt5)
- исключения вызываются только в qt5

-->

Да, это известная особенность Qt5 под Linux. Ты всё верно локализовал.

# Почему только Qt5?

В коде Qt5 эта ошибка была сознательно понижена с Warning до Debug
Комментарий разработчиков Qt буквально гласит:
"Ошибка 'BadWindow' (3) возникает в XCB-бэкенде, когда событие мыши обрабатывается для ресурса, который исчезает между отправкой и обработкой события. Эта ситуация безвредна и может возникать в асинхронных средах."
В GTK эта же ситуация обрабатывается иначе - там напрямую используются XLib вызовы, которые не порождают таких ошибок

# Почему в твоем случае это исключение, а не просто лог?

В Qt5 это предупреждение. В LCL под qt5 это превращается в исключение, потому что:
- Qt5 получает ошибку от X-сервера
- Qt5 логирует её как отладочную информацию
- LCL перехватывает это как сбой XCB и конвертирует в исключение

# Что делать конкретно?

# Вариант А (рекомендую) - глобальный перехват

Код: Выделить всё

Application.OnException := @IgnoreBadWindow;

procedure TfrmMain.IgnoreBadWindow(Sender: TObject; E: Exception);
begin
  if Pos('BadWindow', E.Message) > 0 then
    Exit  // просто игнорируем
  else
    Application.ShowException(E);
end;
# Вариант Б - отключить отладочный вывод Qt5

Код: Выделить всё

export QT_LOGGING_RULES="qt.qpa.xcb=false"
./project1
Итог:
Это безвредная особенность Qt5, которую разработчики самого Qt считают "нормальным поведением в асинхронной среде" .
Просто отлавливай и игнорируй это конкретное исключение.

+

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

-->

Код: Выделить всё

procedure TfrmMain.IgnoreXCBErrors(Sender: TObject; E: Exception);
const
  IgnoreList: array[0..6] of string = (
    'BadWindow',
    'BadDrawable',
    'BadPixmap',
    'BadAlloc',
    'BadAccess',
    'BadMatch',
    'BadValue'
  );
var
  i: Integer;
begin
  for i := 0 to High(IgnoreList) do
    if Pos(IgnoreList[i], E.Message) > 0 then
      Exit;  // игнорируем известные XCB ошибки

  // Остальные исключения показываем
  Application.ShowException(E);
end;
# Расширенный список (включая Qt специфичные):

Код: Выделить всё

const
  IgnoreList: array[0..12] of string = (
    'BadWindow',        // окно уже закрыто
    'BadDrawable',      // рисуемый объект удален
    'BadPixmap',        // пиксельмап не существует
    'BadAlloc',         // нехватка ресурсов X
    'BadAccess',        // попытка доступа к чужому ресурсу
    'BadMatch',         // несоответствие типов в X
    'BadValue',         // неверное значение
    'QXcbConnection',   // общая ошибка XCB
    'XCB error',        // любая XCB ошибка
    'TranslateCoords',  // конкретная операция
    'BadColor',         // цвет удален
    'BadCursor',        // курсор удален
    'BadFont'           // шрифт удален
  );

# Самый простой рабочий вариант:

Код: Выделить всё

procedure TfrmMain.GlobalExceptionHandler(Sender: TObject; E: Exception);
begin
  if (Pos('Bad', E.Message) > 0) or (Pos('XCB', E.Message) > 0) then
    Exit  // игнорируем все X11/XCB ошибки
  else
    Application.ShowException(E);
end;
+

последний вариант - точно безопасный?
-->

Нет, не безопасный.

Код: Выделить всё

Pos('Bad', E.Message) > 0 перехватит слишком много:
- BadWindow       (нужно)
- BadPassword     (ошибка авторизации, надо показывать)
- BadHeader       (ошибка в данных)
- BadRequest      (ошибка HTTP)
Безопасный вариант - только конкретные X11 ошибки:

Код: Выделить всё

procedure TfrmMain.GlobalExceptionHandler(Sender: TObject; E: Exception);
const
  X11Errors: array[0..5] of string = (
    'BadWindow',
    'BadDrawable', 
    'BadPixmap',
    'BadAlloc',
    'BadAccess',
    'BadMatch'
  );
var
  i: Integer;
begin
  for i := 0 to High(X11Errors) do
    if Pos(X11Errors[i], E.Message) > 0 then
      Exit;  // только эти игнорируем

  Application.ShowException(E);
end;
+

в коде можно определить препроцессорными средствами с каким бекендом собирается приложение и сделать соотв. настройку?

-->

Бекенды Lazarus и их директивы

Код: Выделить всё

----------------------------------------
Бекенд    Директива Когда определена
----------------------------------------
GTK2      LCLgtk2   Используется GTK2
GTK3      LCLgtk3   Используется GTK3
Qt4       LCLqt4    Используется Qt4
Qt5       LCLqt5    Используется Qt5
Qt6       LCLqt6    Используется Qt6
Win32     LCLwin32  Windows
Cocoa     LCLcocoa  macOS
----------------------------------------
# Пример кода

Код: Выделить всё

uses LCLType;  // LCLBackendVar

procedure SetupBackendSpecific;
begin
{$IFDEF LCLqt5} // Qt5
Application.OnException := @IgnoreXCBErrors;
WriteLn('Qt5 backend detected');
{$ENDIF}

{$IFDEF LCLgtk2} // GTK2
WriteLn('GTK2 backend detected');
{$ENDIF}

{$IFDEF LCLgtk3} // GTK3
WriteLn('GTK3 backend detected');
{$ENDIF}

{$IFDEF LCLqt4} // Qt4
WriteLn('Qt4 backend detected');
{$ENDIF}
end;
# Где эти директивы определены?

Они автоматически добавляются компилятором при выборе бекенда в:

Код: Выделить всё

Project / Project Options / Compiler Options / Additions and Overrides / LCL Widget Type
Проверить можно в инспекторе:

Код: Выделить всё

uses
  LCLPlatformDef;

// Или через глобальную переменную
WriteLn('Current backend: ', LCLBackendVar);
////////////////////////////////////////////////////////////////////////////////
Аватара пользователя
Alexander
энтузиаст
Сообщения: 882
Зарегистрирован: 18.12.2005 18:10:00
Откуда: оттуда
Контактная информация:

Сообщение Alexander »

случайным образом вываливает access violation и в консоль сообщение:
Ему нужно подробнее показать. Собрать с ключом -gl и запустить в gdb. При останове на AV (или ещё чём-то) набрать bt. И весь листинг запуска через gdb показать ИИ.
Аватара пользователя
sunjob
постоялец
Сообщения: 184
Зарегистрирован: 12.01.2011 14:19:54

Сообщение sunjob »

ну да... он такое насочиняет! я специально дал достаточный минимум, что-бы не было возможности для иного толка. :D
ну а вообще, мне интересно, на сколько близко к истине размышления по "поводу qt5"? так-то он временами даже очень близко к правде глаголит.
Ответить