Russian Qt Forum

Qt => Дополнительные компоненты => Тема начата: Racheengel от Декабрь 05, 2018, 18:17



Название: QSerialPort - странный баг (?)
Отправлено: Racheengel от Декабрь 05, 2018, 18:17
Добрый день,

есть проект, использующий QSerialPort. Данное приложение является slave по отношению к управляющему PC, обе машины соеденены по RS-232 интерфейсу.
Протокол общения - простейший, master-PC раз в несколько секунд присылает 1- или 2- байтовые команды, которые читаются и подтверждаются необходимым ответом.
Заказчик начал ругаться на то, что раз в 1-2 суток происходит остановка коммуникации, иногда с завершением приложения на slave-PC.
После чего требуется физически перестартовать обе машины, чтобы восстановить коммуникацию. Грешили на убитый ком-порт на стороне заказчика.

Самое странное однако началось, когда мы попробовали собрать тестовую систему на базе двух PC, на стороне мастера запустив симуляцию протокола посредством макроса Terraterm.
6 дней подряд система работала без проблем, пока не проявился подобный эффект, как и у заказчика. Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.

Можно ли объяснить данное поведение переполнением буфера отправки QSerialPort, если принимающая сторона перестанет на некоторое время вычитывать из него данные?
Что тогда произойдёт? До какого предела будет "расти" буфер?
Есть ли возможность в таком случае выполнить его насильную очистку (типа clearSendBuffer() или что-то вроде)?


Название: Re: QSerialPort - странный баг (?)
Отправлено: kuzulis от Декабрь 05, 2018, 19:07
Цитировать
Можно ли объяснить данное поведение переполнением буфера отправки QSerialPort, если принимающая сторона перестанет на некоторое время вычитывать из него данные?

Вычитывание ни при чем. Если ты в порт отправляешь данные, то они отправятся даже если провод будет висеть в воздухе.

PS: Какая ОС? Какая Qt? Что за код ошибки у "Cannot write to serial port" ?



Название: Re: QSerialPort - странный баг (?)
Отправлено: Racheengel от Декабрь 05, 2018, 19:28
Да, забыл про самое важное, сорри :(

Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.
Ошибка - SerialPortError::WriteError.

А как тогда "внутри" QSerialPort устроен, он же собирает где-то данные для отправки?
Какой-то физический буфер должен существовать, иначе bytesToWrite() всегда отдавал бы 0.
Или это значение каждый раз читается с железяки?
PS. Исходники пока что не смотрел, просто раньше с портом таких проблем не было, за много лет впервые такое вылезло...


Название: Re: QSerialPort - странный баг (?)
Отправлено: Old от Декабрь 05, 2018, 20:04
Какой контроль потока вы используете? Аппаратный?


Название: Re: QSerialPort - странный баг (?)
Отправлено: Racheengel от Декабрь 05, 2018, 20:10
Какой контроль потока вы используете? Аппаратный?

Никакого. Всё выключено. Скорость 9600, четность выключена, 8 бит данных, 1.5 стопбитов.
Настройки продиктованы системой заказчика (master-PC), они ничего поменять не могут (старющий софт, исходников нету, поддерживать никто не умеет).
 


Название: Re: QSerialPort - странный баг (?)
Отправлено: Old от Декабрь 05, 2018, 20:59
Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Т.е. slave перестает отправлять ответы на запросы мастера? А запросы продолжают приходить штатно от master?
А буфер растет в вашей программе, из которого вы отправляете данные в последовательный порт?

Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.
Прием или отправка?


Название: Re: QSerialPort - странный баг (?)
Отправлено: kuzulis от Декабрь 05, 2018, 22:05
Цитировать
Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.

Слишком старая версия Qt.

Цитировать
Ошибка - SerialPortError::WriteError.

Там она может только в одном случае возникнуть:

Код
C++ (Qt)
bool QSerialPortPrivate::_q_startAsyncWrite()
{
   /// << Если буфер передачи writeBuffer пуст, или если предыдущая операция записи еще не завершилась то ничо не делаем.
   if (writeBuffer.isEmpty() || writeStarted)
       return true;
 
   /// << Копируем все содержимое буфера передачи во временный массив writeChunkBuffer
   /// << и именно его данные передаем в WriteFile, т.к. данные из writeBuffer передавать низя!!!
   writeChunkBuffer = writeBuffer.read();
   ::ZeroMemory(&writeCompletionOverlapped, sizeof(writeCompletionOverlapped));
   if (!::WriteFile(handle, writeChunkBuffer.constData(),
                    writeChunkBuffer.size(), Q_NULLPTR, &writeCompletionOverlapped)) {
 
       QSerialPortErrorInfo error = getSystemError();
       if (error.errorCode != QSerialPort::NoError) {
           if (error.errorCode != QSerialPort::ResourceError)
               error.errorCode = QSerialPort::WriteError; /// << Вот тут именно твоя ошибка и возникает, т.к. больше по коду негде!!!
           setError(error);
           return false;
       }
   }
 
   writeStarted = true;
   return true;
}
 

Цитировать
А как тогда "внутри" QSerialPort устроен, он же собирает где-то данные для отправки?

Внутри QSP есть буффер writeBuffer в который данные добавляются при каждом QSP::write(). Как только данные добавились, то стартует _q_startAsyncWrite(), который дергает WriteFile() с данными из буфера writeBuffer (делается временная копия QByteArray которая передается в ф-ю) и то только если сейчас нет отложенной операции записи. Если уже идет операция записи (например, был ранее вызов) то метод _q_startAsyncWrite() ничего не делает. Как только операция записи завершается (мы узнаем это по IOCP событию на дескрипторе, когда срабатывает нотификатор) то вызывается completeAsyncWrite() и мы обрезаем реально переданные данные из хвоста writeBuffer (освобождаем буффер) и запускаем _q_startAsyncWrite() снова чтобы передать остаток данных из writeBuffer если они есть. Так будет продолжаться пока writeBuffer не опустеет.

Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  :) Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.

По хорошему, ты можешь добавить отладочный вывод кода ошибки Win32 в qserialport_win.cpp:

Код
C++ (Qt)
QSerialPortErrorInfo QSerialPortPrivate::getSystemError(int systemErrorCode) const
{
   if (systemErrorCode == -1)
       systemErrorCode = ::GetLastError();
 
  qDebug() << "System error code:" << systemErrorCode ;
 
  ...
}
 
 

и пресобрать модуль, чтобы посмотреть, почему WriteFile фейлится.

Скорее всего какая-то фигня или с драйвером, или, если это USB CDC девайс, который реализуется на стороне "заказчика" в виде некоей прошивки - то см. что там накосячили. :)

Для теста можно взять QSP, написать программку которая будет просто слать в порт периодически любые данные и смотреть, что будет с ошибкой...

ЗЫ: В общем, разбирайся сам. :)


Название: Re: QSerialPort - странный баг (?)
Отправлено: sergek от Декабрь 06, 2018, 09:56
Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.
Ндаа.. Недавно потратил количество времени, чтобы прийти к такому выводу. А это, оказывается, знание, которое должен знать каждый ((


Название: Re: QSerialPort - странный баг (?)
Отправлено: Racheengel от Декабрь 06, 2018, 11:25
Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Т.е. slave перестает отправлять ответы на запросы мастера? А запросы продолжают приходить штатно от master?
А буфер растет в вашей программе, из которого вы отправляете данные в последовательный порт?

Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.
Прием или отправка?


Да, прекращается отправка со стороны слейва (т.е. наш софт), соотв. приём на стороне мастера.
Запросы идут дальше, мастер по видимому умеет игнорить непришедшие ответы :)
Буфер растёт в QSerialPort.


Название: Re: QSerialPort - странный баг (?)
Отправлено: Racheengel от Декабрь 06, 2018, 11:27
Цитировать
Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.

Слишком старая версия Qt.


Знаю, но обновить несколько проблематично :(


Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  :) Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.


А вот это ценно, я сейчас попробую, спасибо.

С др. стороны, имхо нужно этот функционал в класс QSP по дефолту добавить. Иначе получаются "скрытые знания" :)
Возникла ошибка - стоит флаг autoOpenPortOnFailure()? переоткрываем.



Название: Re: QSerialPort - странный баг (?)
Отправлено: kuzulis от Декабрь 06, 2018, 11:46
Цитировать
Иначе получаются "скрытые знания" Улыбающийся
Возникла ошибка - стоит флаг autoOpenPortOnFailure()? переоткрываем.

Неет ужж. Все в руках самого программиста.


Название: Re: QSerialPort - странный баг (?)
Отправлено: sergek от Июль 01, 2023, 13:15
Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  :) Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.
Появилась необходимость уточнить этот утверждение. На мой взгляд, не всегда нужно переоткрывать порт: например, при получении ошибки QSerialPort::OpenError - зачем это делать, если порт всего лишь говорит, что он уже открыт.
Поэтому у меня вопрос - при каких ошибках, генерируемых QSerialPort, требуется переподключение? Ну, или наоборот - какие не требуют?