Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: sergek от Март 29, 2024, 14:36



Название: Ошибка "Segmentation fault"
Отправлено: sergek от Март 29, 2024, 14:36
Коллеги,
пытаюсь отловить фатальную ошибку SIGSEGV "Segmentation fault". Возникает при выполнении одной из операций и довольно стабильно проявляется (но не всегда). Программа - система мониторинга оборудования, довольно сложная, многопоточная, активно используются сигнально-слотовые связи, работа с БД, с железом и т.д. Операция, на которой ломается программа - это обновление конфигурации оборудования, сводится к остановки сервисов опроса, загрузке новой конфигурации, подключения к оборудованию и запуск сервисов. Есть вложенные циклы событий в разных потоках, да и много чего еще.
Я это упоминаю, чтобы вы хоть небольшое представление имели.

Конкретный вопрос я сформулировать не могу, но, может, подскажете, куда мозги повернуть?
При возникновении ошибок стек вызовов (глубина ~25) мне ничего не дает - вызовы идут из библиотечных qeventloop.cpp,  qeventdispatcher.cpp и завершаются на QCoreApplication::notifyInternal2. И ломается на операторе, отмеченном "==>":

Код
C++ (Qt)
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
   bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
   if (!self && selfRequired)
       return false;
 
   // Make it possible for Qt Script to hook into events even
   // though QApplication is subclassed...
   bool result = false;
   void *cbdata[] = { receiver, event, &result };
   if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {
       return result;
   }
 
   // Qt enforces the rule that events can only be sent to objects in
   // the current thread, so receiver->d_func()->threadData is
   // equivalent to QThreadData::current(), just without the function
   // call overhead.
   QObjectPrivate *d = receiver->d_func();
==> QThreadData *threadData = d->threadData;
   QScopedScopeLevelCounter scopeLevelCounter(threadData);
   if (!selfRequired)
       return doNotify(receiver, event);
   return self->notify(receiver, event);
}

Отлачик дает, вроде, нормальные значения:

Код:
	Локальные переменные		
cbdata @0x1007c500 void*[3]
[0] 0x10d897d0 void*
[1] 0x1007c5a0 void*
[2] 0x1007c51f void*
d 0xabababababababab QObjectPrivate*
event @0x1007c5a0 QTimerEvent
[QEvent] @0x1007c5a0 QEvent
id 28 int
receiver @0x10d897d0 QObject
[vptr] _vptr.QObject
d_ptr 12370169555311111083 QScopedPointer<QObjectData>
staticMetaObject @0x6bbbdfc0 QMetaObject
staticQtMetaObject @0x6bbbe240 QMetaObject
result false bool
scopeLevelCounter <оптимизировано>
selfRequired true bool
Инспектор
Выражения
Возвращаемое значение
Подсказка
doNotify 9892236762171331414 bool (QObject *, QEvent *)
threadData <no such value>


У вас есть мысли, какие возможные причины могут приводить к ошибке на отмеченном операторе?


Название: Re: Ошибка "Segmentation fault"
Отправлено: Apktyc от Март 29, 2024, 14:58
Адрес в d 0xabababababababab выглядит максимально подозрительно и, возможно, намекает на переполнение стека.


Название: Re: Ошибка "Segmentation fault"
Отправлено: kambala от Март 29, 2024, 15:33
попробуй asan / tsan / valgrind


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Март 30, 2024, 11:40
Спасибо за советы, коллеги.


Название: Re: Ошибка "Segmentation fault"
Отправлено: ssoft от Апрель 01, 2024, 08:37
Такое впечатление, что receiver не живой или почти не живой.
Базовый тип QObject выглядит подозрительно.

Нет ли сигнал слот взаимодействия через DirectConnection для объектов, связанных с разными потоками?
Или может сигнал слот взаимодействие через обычный AutoConnection, а потом используется moveToThread()?


Название: Re: Ошибка "Segmentation fault"
Отправлено: Авварон от Апрель 02, 2024, 14:50
Адрес в d 0xabababababababab выглядит максимально подозрительно

посмотри еще чем остальные треды заняты - надо смотреть не вызываются ли деструкторы чего-то в этот момент (собственно ресивера могут в этот момент удалять)


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 02, 2024, 22:27
Такое впечатление, что receiver не живой или почти не живой.
Базовый тип QObject выглядит подозрительно.
На мой взгляд, это вероятная причина. Именно над исключением такой возможности я и работал больше всего. Перед удалением объектов отключал все соединения, очищал мои очереди запросов, закрывал подключения к портам. Это все дает кое-какие результаты, но не 100%.
Такое ощущение, что вызовы сигнал/слот ставятся в очередь, потом я удаляю соединенные объекты (подключения при этом должны автоматически отключиться), и после этого выполняется вызов слота. Интересно, возможно такое? Если да, то это многое объясняет.
Возможно, где-то тут еще порылся deleteLater, но при удалении объектов я его не использую.

Нет ли сигнал слот взаимодействия через DirectConnection для объектов, связанных с разными потоками?
DirectConnection не использую, только AutoConnection и изредка QueuedConnection

Или может сигнал слот взаимодействие через обычный AutoConnection, а потом используется moveToThread()?
А это имеет значение? Если да, то позор мне...  :(

посмотри еще чем остальные треды заняты - надо смотреть не вызываются ли деструкторы чего-то в этот момент (собственно ресивера могут в этот момент удалять)
Я тщательно за этим слежу. Конфигурация, которая пересоздается, находится в потоке ядра программы. Остальные потоки сервисов содержат слоты, выполняющиеся по таймерам, и взаимодействуют с ядром. Перед персозданием конфигурации таймеры останавливаются и взаимодействия с ядром не происходит.
Проверял.

В общем, пока у меня рабочая версия проблемы, что где-то я накосячил в межпотоковом взаимодействии. Хотя раз десять переделывал эти процедуры.
Довел до стабильного результата в случае, если у меня вся периферия отвечает нормально, без таймаутов. Если на шине какое-нибудь устройство ломается и растет очередь запросов, во тут пересоздание конфигурации ведет к краху (но не всегда) Почти верный признак того, о чем говорит ssoft. ;)

Прошу прощения, коллеги, за весьма путанные объяснения.


Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 03, 2024, 08:00
Да, receiver выглядит калечным. Если он убит "хвостом", то куча испорчена. И может за это зацепиться
Цитировать
  // Qt enforces the rule that events can only be sent to objects in
   // the current thread, so receiver->d_func()->threadData is
    // equivalent to QThreadData::current(), just without the function
    // call overhead.
Навесить хвильтр где сравнить receiver->d_func()->threadData и QThreadData::current(), получить там останов


Название: Re: Ошибка "Segmentation fault"
Отправлено: ssoft от Апрель 03, 2024, 14:56
Или может сигнал слот взаимодействие через обычный AutoConnection, а потом используется moveToThread()?
А это имеет значение? Если да, то позор мне...  :(

Такие ошибки чаще всего связаны именно с этим. Значение имеет огромное.

Когда осуществляется connect по AutoConnection выбирается способ соединения в соответствии с потоками обработки событий объектов.
Если потоки одинаковые, то AutoConnection == DirectConnection.

После moveToThread объект меняет поток обработки событий, а соединения остаются связанными через DirectConnection.
События в одном потоке, сигнал слот в другом, отсюда следует UB в случае конкурентного доступа к полям экземпляра объекта.
И программа может упасть не обязательно из-за деструктора, а в любой момент в процессе работы.
А уж если деструктор вызвался, то практически гарантировано упадет (но есть случаи, когда может и не упасть)). UB оно такое).


Название: Re: Ошибка "Segmentation fault"
Отправлено: ssoft от Апрель 03, 2024, 15:21
И ещё... Если иcпользовался moveToThread, то удалять нужно через deleteLater(), чтобы удаление произошло в правильном потоке.


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 03, 2024, 17:10
Да, receiver выглядит калечным. Если он убит "хвостом", то куча испорчена. И может за это зацепиться
Навесить хвильтр где сравнить receiver->d_func()->threadData и QThreadData::current(), получить там останов
Вы, видимо имели в виду QObject::eventFilter? Я плохо разбираю феню кулхацкеров  ;), не очень понял, как это реализовать.

И программа может упасть не обязательно из-за деструктора, а в любой момент в процессе работы.
А уж если деструктор вызвался, то практически гарантировано упадет (но есть случаи, когда может и не упасть)). UB оно такое).
Еще раз проверил, для перемещаемых объектов порядок правильный, например:
Код
C++ (Qt)
   // сервис архива
   archiving->moveToThread(&archivingThread);
   QObject::connect(&archivingThread, &QThread::finished, archiving, &CServiceArchives::deleteLater);
   QObject::connect(this, &CServiceManagement::serviceArchiveSig, archiving, &CServiceArchives::serviceRunSlot);
   QObject::connect(archiving, &CServiceBase::stateChangedSig, this, &CServiceManagement::servStateChangedSlot);
   archivingThread.setObjectName("archiving");
   archivingThread.start();
 

И ещё... Если иcпользовался moveToThread, то удалять нужно через deleteLater(), чтобы удаление произошло в правильном потоке.
Для объектов, для которых использовались сигнально-слотовые соединения, сделал deleteLater - результат аналогичный.

Код я еще перетряс. Самое поганое, что эта функция (обновление конфигурации) стала работать стабильнее, но при определенных условиях все же валится (но не всегда  ;)).
Сделаю паузу, глаз замылился, переключусь на другое. Коллеги, спасибо.



Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 04, 2024, 07:18
Вы, видимо имели в виду QObject::eventFilter? Я плохо разбираю феню кулхацкеров  ;), не очень понял, как это реализовать.
Напр так
Код:
bool MyApp::notify(QObject *object, QEvent *event)
{
  ...
  return QGUIApplication::notify(object, event);
}
И пытаться как-то определить что поле QObjectPrivate * испорчено (ну хотя бы нечетный адрес). Придется подключить приватные хедеры.

С удалением может быть и никак не связано, просто кто-то пишет не туда куда надо, и объект попадает под раздачу. Поэтому первым делом надо проверять кучу (средствами вашей IDE)


Название: Re: Ошибка "Segmentation fault"
Отправлено: ssoft от Апрель 04, 2024, 08:21
Код
C++ (Qt)
QObject::connect(&archivingThread, &QThread::finished, archiving, &CServiceArchives::deleteLater);
 

Цитировать
If deleteLater() is called after the main event loop has stopped, the object will not be deleted.

Что-то подсказывает мне, что archiving никогда не удалиться), цикл обработки событий уже не работает.
Одна морока с этим moveToThread  :-\

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


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 04, 2024, 11:35
И пытаться как-то определить что поле QObjectPrivate * испорчено (ну хотя бы нечетный адрес). Придется подключить приватные хедеры.
Теперь, кажется, понятно - ловить событие от sender`а в receiver`а. Но что мы поймаем, если приемник уже разрушен? В том то и проблема, что мы цепочкой сигнал/слот можем управлять только со стороны объекта, который отправляет событие. А на стороне приемника мы не можем ничего сделать. Если бы соединение сигнал/слот мы бы могли отключать со стороны приемника, проблем бы не возникало.

Что-то подсказывает мне, что archiving никогда не удалится), цикл обработки событий уже не работает.
Удалится. Цикл в main еще работает в то время, как останавливаются потоки. Там у меня работает QtService, а вся программа выполнена в виде процесса.
Не поленился, проверил - деструктор archiving срабатывает (впрочем, я это и раньше проверял, просто убедился еще раз).

Конечно, хотелось бы увидеть весь проект или тестовый пример, или хотя бы стек вызовов по всем потокам.
Увы, сам проект ~50 тыс. строк, сделать минимальный пример - нехилая работа.
Но это уже и не требуется - проблему я локализовал. И она, как сразу мне подсказывали, в том, что "что receiver не живой".
Но пока не решил, что с этим делать.

В двух словах о проблеме, чтобы не было такого: "всем спасибо, все свободны" :).
У меня в составе ядра системы есть программные компоненты, отвечающие за взаимодействие с периферийными устройствами через аппаратные интерфейсы (usb, uart, i2c, ethernet и т.д.). В их задачу входит получение запросов от сервиса опроса или других, его преобразование в соответствии с прикладным протоколом (modbus, can, mqtt и т.д.) в raw-данные, передача в шину и получение ответа устройств. Что-то типа драйверов, я называю их контроллерами интерфейсов. Уверен, вы найдете более правильное название))

Крах программы наблюдается, когда я перед загрузкой новой конфигурации удаляю старые объекты контроллеров, использующих протоколы modbus rtu по протоколу TCP/IP. Это мой компонент (QModbusRtuClient), я сделал его на основе уже имеющихся в составе qtserialbus, исходники я публиковал где-то здесь. Ключевые фрагменты кода (не существенные части я пропускаю, отмечено '...'):

Код
C++ (Qt)
CModbusInterface::CModbusInterface(...) {
...
       //
       modbusDevice = new QModbusRtuClient(this);
...
}
 
CModbusInterface::~CModbusInterface(){
   if (modbusDevice) {
       modbusDevice->deleteLater();
   }
}
 
Объект modbusDevice реализует формирование запросов, передачу в шину, получение ответов и обработку ошибок. Думаю, фрагменты кодов тут не нужны - они есть в документации qt. Главное в том, что в этом объекте есть TCP-сокет, сигналы которого обрабатываются в CModbusInterface.

При удалении старой конфигурации удаляются объекты CModbusInterface. Если на шине все хорошо, все устройства отвечают (время реакции от нескольких мс до нескольких десятков мс), то удаление modbusDevice проходит успешно.
Но если какое-нибудь устройство перестает отвечать, включается механизм  таймаутов и повторов (ретрейнов) - это стандартное поведение компонетов modbus в qt. Т.е. если перед удалением контроллера мой запрос передан в шину, то он в modbusDevice болтается еще как минимум (1+numberOfRetries)*timeout - несколько секунд, после чего сокетом формируется сигнал ошибки и отправляется уже удаленному modbusDevice. И программа валится.

Изменить timeout и numberOfRetries перед удалением не удастся, т.к. они начинают действовать на следующий запрос, и никак не влияют на текущий.
Почему я особо отметил, что используется TCP-сокет: для последовательного порта в библиоеке используется QSerialPort::clear(QSerialPort::AllDirections), что, видимо, обеспечивает при удалении объекта очистку всех буферов и предотвращает формирование сигналов портом. По крайней мере, при работе через последовательные порты у меня программа не валится. Для сокета такой функции нет.

Как решить проблему? На вскидку есть пара вариантов: первый - отложенное удаление старой конфигурации, что-то вроде сборки мусора. Я так делал, мне не понравилось. Второй - не удалять старую конфигурацию, а ее изменять. Наверное, это вполне рабочий вариант, хотя и довольно нудный.

Вот теперь могу сказать: спасибо, коллеги, за помощь.


Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 04, 2024, 12:57
Для человека "вне (вашего) проекта" информативно это
...после чего сокетом формируется сигнал ошибки и отправляется уже удаленному modbusDevice. И программа валится.
И что, Qt позволяет вот так "отправить сигнал удаленному"? Не должно

Тему закрываю.
Зачем? Кому-то мешает?  :)

Одна нормальная тема в год - и ту закрывает  :'(


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 04, 2024, 13:50
Одна нормальная тема в год - и ту закрывает  :'(
Не вопрос, отменил)) Надо бы тогда тему как-то уточнить, чтобы обсуждение пошло в нужном русле.


Название: Re: Ошибка "Segmentation fault"
Отправлено: ssoft от Апрель 04, 2024, 15:23
Крах программы наблюдается, когда я перед загрузкой новой конфигурации удаляю старые объекты контроллеров, использующих протоколы modbus rtu по протоколу TCP/IP. Это мой компонент (QModbusRtuClient), я сделал его на основе уже имеющихся в составе qtserialbus, исходники я публиковал где-то здесь. Ключевые фрагменты кода (не существенные части я пропускаю, отмечено '...'):

Код
C++ (Qt)
CModbusInterface::CModbusInterface(...) {
...
       //
       modbusDevice = new QModbusRtuClient(this);
...
}
 
CModbusInterface::~CModbusInterface(){
   if (modbusDevice) {
       modbusDevice->deleteLater();
   }
}
 

Фрагмент кода не должен приводить к краху по рассматриваемой причине.
Код в деструкторе лишний, так как при конструировании modbusDevice указан родитель this, который и управляет его временем жизни.

после чего сокетом формируется сигнал ошибки и отправляется уже удаленному modbusDevice. И программа валится.

Все сигнал слот соединения разрываются и все сообщения для modbusDevice удаляются из очереди обработки при его удалении.
Поэтому ситуации "сокетом формируется сигнал ошибки и отправляется уже удаленному modbusDevice" при корректной реализации быть не может.

Здесь на лицо проблема конкурентного доступа к экземпляру объекта. Обработка сообщений происходит в одном потоке, удаление объекта в другом.
Можно проверить это с помощью определения своего типа производного QModbusRtuClient, например, и вывода и сравнении информации о потоке в деструкторе и обработчиках сигналов.

Вариантов управления временем жизни не так уж и много:

  • объект на стеке - удалиться при потере области видимости в том же потоке, где был создан, независимо от потока обработки событий
  • член класса - удалиться вместе с экземпляром агрегата, независимо от потока обработки событий
  • умный указатель - удалиться вместе удаления умного указателя, независимо от потока обработки событий
  • Qt parent-child отношение - удалиться вместе с экземпляром родителя, независимо от потока обработки событий
  • new/delete - удалиться в вместе вызова delete, независимо от потока обработки событий
  • deleteLater - удалиться в потока обработки событий, независимо от других способов владения (кроме Qt parent-chaild)


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

Удаление, изменение и т.д. конфигурации не должно приводить в краху программы. Решение задачу с помощью "отложенное удаление старой конфигурации ...", лишь усугубит ситуацию, так как проблема никуда не денется, а частота её появления уменьшиться.





Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 04, 2024, 17:00
Здесь на лицо проблема конкурентного доступа к экземпляру объекта. Обработка сообщений происходит в одном потоке, удаление объекта в другом.
Можно проверить это с помощью определения своего типа производного QModbusRtuClient, например, и вывода и сравнении информации о потоке в деструкторе и обработчиках сигналов.
Только не это. Обработка сообщений и удаление объектов осуществляется в одном потоке - в потоке ядра. Собственно, ядро владеет конфигурацией, его задача - обеспечение асинхронного взаимодействия с периферийным железом.
Запуск обновления конфигурации осуществляется из другого, клиентского потока, в котором живет веб-интерфейс. взаимодействие клиентских потоков с ядром - через сигнал/слотовый механизм. Я использую для этого специальный класс (потоковый адаптер).

По остальным замечаниям - позже, когда освобожусь немного.


Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 05, 2024, 10:55
Маловато "фактов". Хорошо бы получить останов (или хотя бы краш) в своем коде/фильтре. Посмотреть что за QEvent, здесь вариантов немного. Убедиться что это действительно удаление, печатая в ~QModbusRtuClient адрес экземпляра


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 05, 2024, 19:54
Как же я смогу это сделать, если ~QModbusRtuClient уже исполнился, а краш происходит через несколько секунд? Проблема-то в том и состоит, что процессы, приводящие к аварийному завершению, связаны (ну, я так думаю;)) с сокетом, а приемник уже уничтожен.
Прямо вот сейчас пришла мысль - можно было бы создать отладочный объект-приемник, который живет все время работы программы, и подключить его к возможным источникам сигналов. Но поздно - я уже все переделываю, не хочется возвращаться к проблеме, которая имеет чисто академический интерес))


Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 06, 2024, 13:37
Как же я смогу это сделать, если ~QModbusRtuClient уже исполнился, а краш происходит через несколько секунд?
напр так
Код:
QModbusRtuClient::~QModbusRtuClient( void )
{
 printf("~QModbusRtuClient %p\n", this);
}
И при краше сравнить receiver c напечатанным.
Но поздно - я уже все переделываю, не хочется возвращаться к проблеме, которая имеет чисто академический интерес))
Хорошо если так  :)


Название: Re: Ошибка "Segmentation fault"
Отправлено: sergek от Апрель 09, 2024, 16:13
И при краше сравнить receiver c напечатанным.
Как-то не сразу до меня дошло, что адрес receiver можно взять из стека)) Ну ладно, пригодится на будущее, спасибо.
Хорошо бы где-нибудь почитать про методы отладки в Qt - наработанные приемы, инструменты. Может, есть монография по этой теме?


Название: Re: Ошибка "Segmentation fault"
Отправлено: Igor_S от Апрель 09, 2024, 16:52
Хорошо бы где-нибудь почитать про методы отладки в Qt - наработанные приемы, инструменты. Может, есть монография по этой теме?
О монографии не слышал. Кстати надо печатать и в конструкторе, возможна ABA. Вообще первое что нужно сделать - добиться стабильности краша, идеи/мысли для этого у Вас есть. Если стабильно летит - дальше уже "дело техники" и все такое. Самое мерзкое если появится так раз в месяц - и с приветом, вымотает все кишки  :'(