исходные:
Код: Выделить всё
- 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
изначально было "заблокировано" изменение размеров формы (тем или иным спопобом).
по ходу разборок/тестовых запусков - замечается а-периодическое вываливание исключения:
- только в 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)
# Что происходит
- Ошибка 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
+
вываливает исключиение с соответствующим 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 безопаснее
Код: Выделить всё
// Если вызываешь ProcessMessages в цикле
if not Application.Terminated then
Application.ProcessMessages;
3. Отключи X ошибки в LCL / Можно попробовать:
Код: Выделить всё
uses
LCLIntf;
// В начале программы
SetExceptionMask(GetExceptionMask + [exInvalidOp, exOverflow, exUnderflow]);
Если исключение появляется редко, самый простой способ - добавить обработчик:
Код: Выделить всё
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;
Release планирует удаление после завершения текущего цикла обработки событий:
Код: Выделить всё
Form.Release; // безопаснее, чем Free
Код: Выделить всё
Application.OnException := @HandleException;
procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
if Pos('BadWindow', E.Message) > 0 then
// Игнорируем эту ошибку
else
Application.ShowException(E);
end;
Если есть TTimer или Application.QueueAsyncCall, которые работают с формой после её закрытия.
# Главный вопрос:
Исключение всегда вылетает в одном и том же месте?
Или случайно?
Если случайно - скорее всего гонка, и без глобального перехвата не обойтись.
+
уточнение:
- тестируются 3 бекэнда (gtk/qt4/qt5)
- исключения вызываются только в qt5
-->
Да, это известная особенность Qt5 под Linux. Ты всё верно локализовал.
# Почему только Qt5?
В коде Qt5 эта ошибка была сознательно понижена с Warning до Debug
Комментарий разработчиков Qt буквально гласит:
В GTK эта же ситуация обрабатывается иначе - там напрямую используются XLib вызовы, которые не порождают таких ошибок"Ошибка 'BadWindow' (3) возникает в XCB-бэкенде, когда событие мыши обрабатывается для ресурса, который исчезает между отправкой и обработкой события. Эта ситуация безвредна и может возникать в асинхронных средах."
# Почему в твоем случае это исключение, а не просто лог?
В 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;
Код: Выделить всё
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;
Код: Выделить всё
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)
Код: Выделить всё
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);
