Russian Qt Forum
Ноябрь 23, 2024, 14:39 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3   Вниз
  Печать  
Автор Тема: Проблема с сервером (deleteLater() для сокета вызывает SegFault)  (Прочитано 18587 раз)
Ground
Гость
« : Октябрь 09, 2012, 12:16 »

Доброго времени суток!
У меня имеется обычный такой сервер, полностью писанный на Qt. В последнее время, количество запросов к нему увеличилось, и сервер начал падать.
Прежде всего, стоит пару слов сказать о том, в каких случаях падает сервер. Случаются моменты, когда поток запросов на сервер довольно значительный. Клиент подключается, что-то передает (порядка 100Б), отключается. В коде для подключенного клиента выделяется сокет. Как только клиент отключается - мы по сигналу disconnected() от этого сокета выполняем отключение, в т.ч. и удаляем память. В коде это выглядит так:

При подключении клиента:
Код
C++ (Qt)
connect(mClients[clientNumber].data(), SIGNAL(disconnected()),
           this, SLOT(slotRejectClient()));

При вызове слота:
Код
C++ (Qt)
// Отключение клиента
int Server::slotRejectClient()
{
   int clientNumber = -1;
   // Поиск клиента, которого требуется отключить
   for (int i = 0; i < mMaxClients; i++)
   {
       if (mClients[i].data() != qobject_cast<QTcpSocket*>(sender()))
           continue;
       clientNumber = i;
       break;
   }
 
   if (clientNumber < 0)
       return -1;
 
   return this->disconnectClient(clientNumber);
}

Ну и наконец чистка памяти:
Код
C++ (Qt)
// Отключение и удаление клиента
int Server::disconnectClient(quint16 clientNumber)
{
   disconnect(mClients[clientNumber].data(), SIGNAL(readyRead()),
              this, SLOT(slotReceiveData()));
   disconnect(mClients[clientNumber].data(), SIGNAL(disconnected()),
              this, SLOT(slotRejectClient()));
   disconnect(mClients[clientNumber].data(), SIGNAL(error(QAbstractSocket::SocketError)),
              this, SLOT(slotClientSocketError(QAbstractSocket::SocketError)));
 
   if (!mClients[clientNumber].isNull())
   {
       // Сокет
       mClients[clientNumber].data()->close();
       mClients[clientNumber].data()->deleteLater();
       mClients[clientNumber] = 0;
   }
 
   emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber));
 
   return 0;
}

Так вот, проблема в том, что в функции disconnectClient() при вызове mClients[clientNumber].data()->deleteLater(); сервер падает с сегфолтом. По стеку - вообще ничего не видно, одни системные вызовы. Если убрать строку - сервер работает отлично, правда утекает память.

Стек вызовов на момент падения:
Код
C++ (Qt)
0 QAbstractSocketPrivate::canReadNotification qabstractsocket.cpp 672 0x6cbc0172
1 QAbstractSocketPrivate::readNotification qabstractsocket_p.h 77 0x6cbff651
2 QAbstractSocketEngine::readNotification qabstractsocketengine.cpp 168 0x6cbb3855
3 QReadNotifier::event qnativesocketengine.cpp 1151 0x6cbcc009
4 QApplicationPrivate::notify_helper qapplication.cpp 4554 0xa8c020
5 QApplication::notify qapplication.cpp 3936 0xa89973
6 QCoreApplication::notifyInternal qcoreapplication.cpp 876 0x69dd3b76
7 QCoreApplication::sendEvent qcoreapplication.h 231 0x69e4488c
8 qt_internal_proc qeventdispatcher_win.cpp 484 0x69df92b7
9 USER32!IsWindowVisible C:\Windows\system32\user32.dll 0 0x76f3c4e7
10 QCharRef::operator= qstring.h 799 0x12112ca
11 USER32!IsWindowVisible C:\Windows\system32\user32.dll 0 0x76f3c5e7
12 qt_fast_timer_proc qeventdispatcher_win.cpp 428 0x69df90ec
13 USER32!IsWindowVisible C:\Windows\system32\user32.dll 0 0x76f3cc19
14 ?? 0
 
 

Никто не сталкивался с подобной проблемой?

PS: Забыл про один момент. Сервер в последнее время стал сыпать предупреждением
QSocketNotifier: Invalid socket specified
QSocketNotifier: Internal error
Не знаю, может быть это важно и относится к моей проблеме. Поковырявшись в исходниках QSocketNotifier, удалось выяснить, что Invalid socket specified - ошибка, появляющаяся при отрицательном и некорректном дескрипторе. Откуда он берется в QSocketNotifier - хрен его знает.
« Последнее редактирование: Октябрь 09, 2012, 12:21 от Ground » Записан
_OLEGator_
Гость
« Ответ #1 : Октябрь 09, 2012, 12:30 »

На первый взгляд меня только смущает зануление элемента в списке, по нормальному его надо удалять из списка, а не просто занулять.
И в функции slotRejectClient() идет перебор этого массива, в котором отсутствует проверка mClients[ i ] на NULL, тут скорее всего и происходит обращение к зануленному элементу.
Записан
Ground
Гость
« Ответ #2 : Октябрь 09, 2012, 12:44 »

На первый взгляд меня только смущает зануление элемента в списке, по нормальному его надо удалять из списка, а не просто занулять.
И в функции slotRejectClient() идет перебор этого массива, в котором отсутствует проверка mClients[ i ] на NULL, тут скорее всего и происходит обращение к зануленному элементу.
Посыпаю голову пеплом, у меня фиксированный массив для сокетов. Ну это потом переделаю.
Причем массив не для сокетов, а для QPointer<QTcpSocket>. Поэтому строка:
Код
C++ (Qt)
mClients[i].data() != qobject_cast<QTcpSocket*>(sender())
должна корректно отрабатывать. Хотя в целях эксперимента я переписал строку вот так:
Код
C++ (Qt)
mClients[i].isNull() || mClients[i].data() != qobject_cast<QTcpSocket*>(sender())
Падения продолжаются.

Вот содержимое qabstractsocket.cpp на строке 672.
Код
C++ (Qt)
   // reset the read socket notifier state if we reentered inside the
   // readyRead() connected slot.
   if (readSocketNotifierStateSet && socketEngine &&
       readSocketNotifierState != socketEngine->isReadNotificationEnabled()) {
       socketEngine->setReadNotificationEnabled(readSocketNotifierState); // Тут у нас socketEngine = @0xfeeefeee
       readSocketNotifierStateSet = false;
   }
Эта функция отвечает за отправку сигнала readyRead(). Причем на 672 строку мы попадаем только в том случае, если уже находимся в слоте, зацепленном на readyRead() сокета. Все страньше и страньше. Я еще мог бы допустить такую ошибку, если бы у меня сокет осуществлял получение данных в одном потоке, а удалял я его в другом. Но в моем случае все действия происходят в одном потоке.
Дополнительно выяснилось, что socketEngine в момент падения имеет значение 0xfeeefeee. Согласно вики 0xfeeefeee -   Used by Microsoft's HeapFree() to mark freed heap memory.
У меня есть чувство, что память помечается освобожденной, но проверку, которая это должна выявить (if .. && socketEngine && ...) код все равно проходит. Видится мне, в *nix это было бы по-другому.
« Последнее редактирование: Октябрь 09, 2012, 13:03 от Ground » Записан
_OLEGator_
Гость
« Ответ #3 : Октябрь 09, 2012, 13:45 »

а разве проверки mClients[ i ].data() и mClients[ i ] == NULL идентичны?
и программа не упадет на строке mClients[ i ].data(), если mClients[ i ] == NULL?
Записан
Ground
Гость
« Ответ #4 : Октябрь 09, 2012, 13:50 »

а разве проверки mClients[ i ].data() и mClients[ i ] == NULL идентичны?
и программа не упадет на строке mClients[ i ].data(), если mClients[ i ] == NULL?
К mClients[ i ] == NULL идентичная проверка будет mClients.data() == Null или mClients.isNull()
Просто data() - это метод QPointer(), он не вызывает сегфолта в любом случае.
Записан
Patrin Andrey
Гость
« Ответ #5 : Октябрь 09, 2012, 14:56 »

О да, сколько же я ловил у себя этот баг:)
Проблема в том(У меня так была) что вы делаете deleteLatter, потом эмитите сигнал emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); это сиграл принимается кем-то. Этот кто-то делает действия(к примеру QCoreApplication::processEvents), которые приводят к тому, что ваш deleteLatter обработается до того как управление вернётся в int Server::disconnectClient(quint16 clientNumber). После этого управление возвращается в функцию внутри qabstractsocket но объект уже удалён.
Записан
Ground
Гость
« Ответ #6 : Октябрь 09, 2012, 15:56 »

О да, сколько же я ловил у себя этот баг:)
Проблема в том(У меня так была) что вы делаете deleteLatter, потом эмитите сигнал emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); это сиграл принимается кем-то. Этот кто-то делает действия(к примеру QCoreApplication::processEvents), которые приводят к тому, что ваш deleteLatter обработается до того как управление вернётся в int Server::disconnectClient(quint16 clientNumber). После этого управление возвращается в функцию внутри qabstractsocket но объект уже удалён.

И как вы решили данную проблему? Вполне может быть похожая ситуация и у меня, я правда попробовал вызывать deleteLater следующим образом:
Код
C++ (Qt)
QTimer::singleShot(1000, mClients[clientNumber].data(), SLOT(deleteLater()));
Но проблема все равно сохранилась, к сожалению.

UPD: Видимо я поторопился с выводами, если выставить 5000мс, сервер вроде бы работает стабильно
« Последнее редактирование: Октябрь 09, 2012, 16:01 от Ground » Записан
Patrin Andrey
Гость
« Ответ #7 : Октябрь 09, 2012, 16:05 »

Попробуйте.
emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber));
до deleteLatter.

Удалять по таймеру, это жесть. Потом баги ловить будете ещё дольше.
Записан
Ground
Гость
« Ответ #8 : Октябрь 09, 2012, 16:52 »

Попробуйте.
emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber));
до deleteLatter.

Удалять по таймеру, это жесть. Потом баги ловить будете ещё дольше.
Я пробовал вообще убрать все сигналы после удаления, все равно не работает. Только с таймером. Выбор у меня не велик - либо оставлять сервер с утечками, либо удалять по таймеру.
Сейчас еще потестирую сервер на минимальном проекте, может еще какую-нибудь зависимость найду.
Записан
Patrin Andrey
Гость
« Ответ #9 : Октябрь 09, 2012, 18:15 »

Вспомнил ещё подробности. У меня сигнал эмитился из слота, который присоединён к readyRead. Покажите код slotReceiveData().
Записан
Ground
Гость
« Ответ #10 : Октябрь 09, 2012, 23:04 »

Вспомнил ещё подробности. У меня сигнал эмитился из слота, который присоединён к readyRead. Покажите код slotReceiveData().

Вот это уже интереснее, т.к. ошибка в qabstractsocket.cpp связана с этим слотом. Вот код:
Код
C++ (Qt)
// Получение данных от клиента, обработка
int Server::slotReceiveData()
{
   bool result = false;
   int clientNumber = 0;
   // Поиск клиента, который выслал данных о MMF
   for (int i = 0; i < mMaxClients; i++)
   {
       if (mClients[i].data() != qobject_cast<QTcpSocket*>(sender()))
           continue;
       result = true;
       clientNumber = i;
       break;
   }
 
   if (!result)
       return -1;
 
   quint16 sizeOfPackage = 0;
   quint8 packageType = 0;
   QByteArray package;
 
   // Парсинг всех пришедших данных
   while (true)
   {
       // Считывание пришедших данных
       if (!mClients[clientNumber].isNull())
           mReceivedData[clientNumber].append(mClients[clientNumber].data()->readAll());
 
       // Проверка на условие: принято недостаточно данных
       if (mReceivedData[clientNumber].size() < (quint16)sizeof(quint16))
           break;
 
       // Получение количества отправленных байт (0 - 1 байты)
       memcpy(&sizeOfPackage,
              mReceivedData[clientNumber].data(),
              sizeof(quint16));
 
       // Проверка на условие: принят не весь пакет целиком
       if (mReceivedData[clientNumber].size() < sizeOfPackage || sizeOfPackage == 0)
           break;
 
       // Получение типа сообщения (2ой байт)
       memcpy(&packageType,
              mReceivedData[clientNumber].data() + sizeof(quint16),
              sizeof(quint8));
 
       // Получение основной части пакета
       const int specialData = sizeof(quint16) + sizeof(quint8);
       package = mReceivedData[clientNumber].mid(specialData,
                                                 sizeOfPackage - specialData);
 
       // Удаление распарсенных данных
       mReceivedData[clientNumber] = mReceivedData[clientNumber].mid(sizeOfPackage);
 
       // Обработка пакета
       switch (packageType)
       {
       case PackageType::Command:
           result = this->parseCommandPackage(clientNumber, package);
           break;
       case PackageType::RegInfo:
           result = this->parseRegInfoPackage(clientNumber, package);
           break;
       case PackageType::MMFName:
           result = this->parseMMFPackage(clientNumber, package);
           break;
       case PackageType::DescriptionRequest:
           break;
       case PackageType::CoefficientRequest:
           break;
       }
 
       if (!result)
           break;
   }
 
   if (!result)
       this->slotDisconnectClient(clientNumber);
 
   return 0;
}
Записан
Ground
Гость
« Ответ #11 : Октябрь 09, 2012, 23:26 »

Пока не берусь говорить со 100% уверенностью, но кажется, с вашей помощью, я нашел причину падений.
Вы были правы, когда говорили об некорректной обработке очереди сообщений. Сегодня утром вспомнил про один хинт, который я добавил давно, чтобы избежать фриза UI:
Код
C++ (Qt)
// Запись информации в журнал
int MainWindow::slotAddTextToLog(const QString text)
{
   teLog->append(QDate::currentDate().toString("dd.MM.yyyy") +
                 " " +
                 QTime::currentTime().toString() + " - " + text);
 
   QCoreApplication::processEvents(); // Вот беда, судя по всему этот вызов обрабатывает не только UI-сообщения. Видимо в нем и была проблема
 
   return 0;
}

Пока еще потестирую и обязательно отпишусь.
« Последнее редактирование: Октябрь 09, 2012, 23:30 от Ground » Записан
Ground
Гость
« Ответ #12 : Октябрь 10, 2012, 07:11 »

Все протестировал, все работает. Остался один нерешенный вопрос.
Я из слота, работающего с UI (вывод текста) удалил следующую строку:
Код
C++ (Qt)
QCoreApplication::processEvents();
По хорошему нужно сделать так, чтобы обрабатывались все события, кроме DeferredDelete. Но судя по документации, такой возможности совсем нет. Причем даже непонятно, к какому классу событий относится DeferredDelete. Вот оттуда цитата:
Код:
QEventLoop::AllEvents - All events. Note that DeferredDelete events are processed specially. See QObject::deleteLater() for more details.
QEventLoop::ExcludeUserInputEvents - Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.
QEventLoop::ExcludeSocketNotifiers - Do not process socket notifier events. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeSocketNotifiers flag.

Как же все таки можно исключить из обработки событие удаления объекта?
Записан
LisandreL
Птица говорун
*****
Offline Offline

Сообщений: 984


Надо улыбаться


Просмотр профиля
« Ответ #13 : Октябрь 10, 2012, 07:55 »

Ну давайте посмотрим. Само по себе от удаления объекта ничего упасть не может. Должно быть обращение к нему (ну или к подобъектам, если судить по стеку).
Значит что? Что мы хотим в нашем processEvents мы не хотим допустить обращения к сокету.
Какой флаг нам нужен? Правильно, QEventLoop::ExcludeSocketNotifiers.
Записан
Patrin Andrey
Гость
« Ответ #14 : Октябрь 10, 2012, 08:14 »

Лучше создайте 2 сигнала
signalSendTextToLogPrivate(QString);
signalSendTextToLog(QString);
и законнектите их друг к другу вот так
connect(this, SIGNAL(signalSendTextToLogPrivate(QString)) , this, SIGNAL(signalSendTextToLog(QString)), Qt::QueuedConnection)

В Server::disconnectClient делайте
emit signalSendTextToLogPrivate(QString("Клиент №%1 отключен!").arg(clientNumber));
в итоге все кто приконнекчен к signalSendTextToLog получат управление после выхода из ваших слотов.
Записан
Страниц: [1] 2 3   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.309 секунд. Запросов: 22.