Название: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 12:16 Доброго времени суток!
У меня имеется обычный такой сервер, полностью писанный на Qt. В последнее время, количество запросов к нему увеличилось, и сервер начал падать. Прежде всего, стоит пару слов сказать о том, в каких случаях падает сервер. Случаются моменты, когда поток запросов на сервер довольно значительный. Клиент подключается, что-то передает (порядка 100Б), отключается. В коде для подключенного клиента выделяется сокет. Как только клиент отключается - мы по сигналу disconnected() от этого сокета выполняем отключение, в т.ч. и удаляем память. В коде это выглядит так: При подключении клиента: Код
При вызове слота: Код
Ну и наконец чистка памяти: Код
Так вот, проблема в том, что в функции disconnectClient() при вызове mClients[clientNumber].data()->deleteLater(); сервер падает с сегфолтом. По стеку - вообще ничего не видно, одни системные вызовы. Если убрать строку - сервер работает отлично, правда утекает память. Стек вызовов на момент падения: Код
Никто не сталкивался с подобной проблемой? PS: Забыл про один момент. Сервер в последнее время стал сыпать предупреждением QSocketNotifier: Invalid socket specified QSocketNotifier: Internal error Не знаю, может быть это важно и относится к моей проблеме. Поковырявшись в исходниках QSocketNotifier, удалось выяснить, что Invalid socket specified - ошибка, появляющаяся при отрицательном и некорректном дескрипторе. Откуда он берется в QSocketNotifier - хрен его знает. Название: Re: Проблема с сервером (deleteLater() для сокета вызывае Отправлено: _OLEGator_ от Октябрь 09, 2012, 12:30 На первый взгляд меня только смущает зануление элемента в списке, по нормальному его надо удалять из списка, а не просто занулять.
И в функции slotRejectClient() идет перебор этого массива, в котором отсутствует проверка mClients[ i ] на NULL, тут скорее всего и происходит обращение к зануленному элементу. Название: Re: Проблема с сервером (deleteLater() для сокета вызывае Отправлено: Ground от Октябрь 09, 2012, 12:44 На первый взгляд меня только смущает зануление элемента в списке, по нормальному его надо удалять из списка, а не просто занулять. Посыпаю голову пеплом, у меня фиксированный массив для сокетов. Ну это потом переделаю.И в функции slotRejectClient() идет перебор этого массива, в котором отсутствует проверка mClients[ i ] на NULL, тут скорее всего и происходит обращение к зануленному элементу. Причем массив не для сокетов, а для QPointer<QTcpSocket>. Поэтому строка: Код должна корректно отрабатывать. Хотя в целях эксперимента я переписал строку вот так: Код Падения продолжаются. Вот содержимое qabstractsocket.cpp на строке 672. Код Эта функция отвечает за отправку сигнала readyRead(). Причем на 672 строку мы попадаем только в том случае, если уже находимся в слоте, зацепленном на readyRead() сокета. Все страньше и страньше. Я еще мог бы допустить такую ошибку, если бы у меня сокет осуществлял получение данных в одном потоке, а удалял я его в другом. Но в моем случае все действия происходят в одном потоке. Дополнительно выяснилось, что socketEngine в момент падения имеет значение 0xfeeefeee. Согласно вики 0xfeeefeee - Used by Microsoft's HeapFree() to mark freed heap memory. У меня есть чувство, что память помечается освобожденной, но проверку, которая это должна выявить (if .. && socketEngine && ...) код все равно проходит. Видится мне, в *nix это было бы по-другому. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: _OLEGator_ от Октябрь 09, 2012, 13:45 а разве проверки mClients[ i ].data() и mClients[ i ] == NULL идентичны?
и программа не упадет на строке mClients[ i ].data(), если mClients[ i ] == NULL? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 13:50 а разве проверки mClients[ i ].data() и mClients[ i ] == NULL идентичны? К mClients[ i ] == NULL идентичная проверка будет mClients.data() == Null или mClients.isNull()и программа не упадет на строке mClients[ i ].data(), если mClients[ i ] == NULL? Просто data() - это метод QPointer(), он не вызывает сегфолта в любом случае. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 09, 2012, 14:56 О да, сколько же я ловил у себя этот баг:)
Проблема в том(У меня так была) что вы делаете deleteLatter, потом эмитите сигнал emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); это сиграл принимается кем-то. Этот кто-то делает действия(к примеру QCoreApplication::processEvents), которые приводят к тому, что ваш deleteLatter обработается до того как управление вернётся в int Server::disconnectClient(quint16 clientNumber). После этого управление возвращается в функцию внутри qabstractsocket но объект уже удалён. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 15:56 О да, сколько же я ловил у себя этот баг:) Проблема в том(У меня так была) что вы делаете deleteLatter, потом эмитите сигнал emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); это сиграл принимается кем-то. Этот кто-то делает действия(к примеру QCoreApplication::processEvents), которые приводят к тому, что ваш deleteLatter обработается до того как управление вернётся в int Server::disconnectClient(quint16 clientNumber). После этого управление возвращается в функцию внутри qabstractsocket но объект уже удалён. И как вы решили данную проблему? Вполне может быть похожая ситуация и у меня, я правда попробовал вызывать deleteLater следующим образом: Код Но проблема все равно сохранилась, к сожалению. UPD: Видимо я поторопился с выводами, если выставить 5000мс, сервер вроде бы работает стабильно Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 09, 2012, 16:05 Попробуйте.
emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); до deleteLatter. Удалять по таймеру, это жесть. Потом баги ловить будете ещё дольше. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 16:52 Попробуйте. Я пробовал вообще убрать все сигналы после удаления, все равно не работает. Только с таймером. Выбор у меня не велик - либо оставлять сервер с утечками, либо удалять по таймеру.emit signalSendTextToLog(QString("Клиент №%1 отключен!").arg(clientNumber)); до deleteLatter. Удалять по таймеру, это жесть. Потом баги ловить будете ещё дольше. Сейчас еще потестирую сервер на минимальном проекте, может еще какую-нибудь зависимость найду. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 09, 2012, 18:15 Вспомнил ещё подробности. У меня сигнал эмитился из слота, который присоединён к readyRead. Покажите код slotReceiveData().
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 23:04 Вспомнил ещё подробности. У меня сигнал эмитился из слота, который присоединён к readyRead. Покажите код slotReceiveData(). Вот это уже интереснее, т.к. ошибка в qabstractsocket.cpp связана с этим слотом. Вот код: Код
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 09, 2012, 23:26 Пока не берусь говорить со 100% уверенностью, но кажется, с вашей помощью, я нашел причину падений.
Вы были правы, когда говорили об некорректной обработке очереди сообщений. Сегодня утром вспомнил про один хинт, который я добавил давно, чтобы избежать фриза UI: Код
Пока еще потестирую и обязательно отпишусь. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 07:11 Все протестировал, все работает. Остался один нерешенный вопрос.
Я из слота, работающего с UI (вывод текста) удалил следующую строку: Код По хорошему нужно сделать так, чтобы обрабатывались все события, кроме DeferredDelete. Но судя по документации, такой возможности совсем нет. Причем даже непонятно, к какому классу событий относится DeferredDelete. Вот оттуда цитата: Код: QEventLoop::AllEvents - All events. Note that DeferredDelete events are processed specially. See QObject::deleteLater() for more details. Как же все таки можно исключить из обработки событие удаления объекта? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: LisandreL от Октябрь 10, 2012, 07:55 Ну давайте посмотрим. Само по себе от удаления объекта ничего упасть не может. Должно быть обращение к нему (ну или к подобъектам, если судить по стеку).
Значит что? Что мы хотим в нашем processEvents мы не хотим допустить обращения к сокету. Какой флаг нам нужен? Правильно, QEventLoop::ExcludeSocketNotifiers. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 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 получат управление после выхода из ваших слотов. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 10, 2012, 08:20 Ну давайте посмотрим. Само по себе от удаления объекта ничего упасть не может. Должно быть обращение к нему (ну или к подобъектам, если судить по стеку). Проблема в том, что может быть сложно контролировать все слоты подключённые к signalSendTextToLog (или другим сигналам, которые мы будем эмитить). В итоге эти грабли будут всплывать опять и опять.Значит что? Что мы хотим в нашем processEvents мы не хотим допустить обращения к сокету. Какой флаг нам нужен? Правильно, QEventLoop::ExcludeSocketNotifiers. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 10:28 В итоговом решение объединил два совета. Сделал 2 сигнала и соединил их через Qt::QueuedConnection, а в слоте работы с UI добавил processEvent с параметром QEventLoop::ExcludeSocketNotifiers.
Если применять первое решение с 2 сигналами - программа падает и по стеку вызов видно, что ошибка где-то в moc-файле. Я там уже разбираться не стал. Само падение происходит при обращении к параметру сигнала text. Если применять второе решение с QEventLoop::ExcludeSocketNotifiers - программа работает стабильно, UI не виснет, но сервер частенько бросает предупреждения вида: Код Что, в принципе, логично, учитывая, что мы удаляем объект, а потом только обрабатываем уведомления от сокета. Если же объединить два варианта, сервер ведет себя стабильно, при полной загрузке работает уже час без сбоев. В случае же, если при увеличении нагрузке вылезет подобный баг - либо полезу разбираться дальше глубже, в причине проблемы, либо просто уменьшу вывод информации в лог сервера и отключу функцию processEvents(). Одним словом, теперь я знаю где искать :) Вопрос можно считать закрытым. Спасибо всем, кто помогал. И отдельное, большое человеческое спасибо, Patrin Andrey, сам бы вряд-ли докопался до истинной причины бага :) Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 10:31 Я не думаю что ты докопался до истинной причины бага. Тебе не приходила в голову мысль, что раз приходится писать такие странные костыли для несложного, в общем-то, действия, то у тебя что-то сильно не так с подходом?
З.Ы. По теме не мог помочь, потому что код, на мой взгляд, странный весь Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 10:32 З.Ы. По теме не мог помочь, потому что код, на мой взгляд, странный весь Ну так вы подскажите, в чем странность?Плюс я бы не сказал, что это костыли. Точнее не так. Если уж говорить про костыли, то это - processEvents(). Не было бы этой функции, код отрабатывал бы нормально. А чтобы ее не было - нужно было изначально делать сервер многопоточным. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 10:36 З.Ы. По теме не мог помочь, потому что код, на мой взгляд, странный весь Ну так вы подскажите, в чем странность?На мой взгляд странность во всем. Например, disconnect не нужен. Не удалять пустой указатель из списка ммм... необычно. Какой смысл в QPointer, если ты вручную управляешь жизнью сокета? Зачем делать close(), если сокет и так уже отключился? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 10:50 З.Ы. По теме не мог помочь, потому что код, на мой взгляд, странный весь Ну так вы подскажите, в чем странность?На мой взгляд странность во всем. Например, disconnect не нужен. Не удалять пустой указатель из списка ммм... необычно. Какой смысл в QPointer, если ты вручную управляешь жизнью сокета? Зачем делать close(), если сокет и так уже отключился? Ну я же тоже это все не просто написал. disconnect() в данном случае нужен для того, чтобы во время удаления сокета не получить другой сигнал, на прием/чтение данных. Ведь согласно документации, автоотключение происходит лишь при уничтожении объекта. А сколько пройдет до этого момента? Я уж лучше отключу сигнал вручную. Были у меня случаи с таким багом. close() нужен для тех случаев, когда disconnectClient() вызывается не из слота rejectClient, а, допустим, во время остановки сервера. В close() стоит проверка, не находится ли сокет в состоянии закрытия, так что мы ничего не теряем при вызове этой функции, зато уменьшаем объем нашего кода. Насчет не удаления пустого указателя - я голову уже пеплом выше посыпал, у меня фиксированный массив клиентов. Т.е. в конфиге написали - 512, лимит - 512. И где я управляю жизнью сокета? То, что я его зануляю? Это привычка, и я не назову ее плохой. Пусть уж лучше у меня будет нулевой указатель и QPointer со значением NULL, чем указатель непонять куда и опять же QPointer == NULL. В обоих случаях же, сокет уведомит QPointer о своем разрушении. В общем, мне так спокойнее. Сейчас вы указали лишь некоторые кода недостатки (для вас), которые вряд ли являются критическими для восприятия проблемы в целом. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 10:54 Для Patrin Andrey они не являются критическими, а вот для меня являются. Я не хочу критиковать твой код, а хочу обратить внимание на то, что создание таких костылей, которыми является итоговое решение, может быть результатом неправильного подхода к написанию сервера
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 11:03 Для Patrin Andrey они не являются критическими, а вот для меня являются. Я не хочу критиковать твой код, а хочу обратить внимание на то, что создание таких костылей, которыми является итоговое решение, может быть результатом неправильного подхода к написанию сервера Ну так и я же пытаюсь понять, что мне исправить, чтобы получить правильный сервер. Пока что я только вынес для себя одно замечание - сменить фиксированный массив с сокетами на список. Ваш пример с QPointer - это дело личного вкуса, к серверу же не относится. С ручным отключением сигналов. Ну я вот так прикинул, можно было в самом слоте получения данных сделать проверку, находится ли сокет в состоянии Connected. Это вы считаете правильным решением приведенной мной проблемы? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 11:07 Блин... Придется удалить одно из своих сообщений, видимо...
Еще раз. То, что я перечислил - это моменты, мешающие лично мне ответить на вопрос "почему SegFault и как его избежать?". Что может быть не так с твоим подходом я не знаю как минимум по двум причинам: 1) не видно всего кода, а есть только кусок с удалением 2) не факт что я сам знаю правильных подход, но костыльность решения намекает что он может отличаться от твоего. Так понятно? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 11:11 Блин... Придется удалить одно из своих сообщений, видимо... Более чем. Еще раз. То, что я перечислил - это моменты, мешающие лично мне ответить на вопрос "почему SegFault и как его избежать?". Что может быть не так с твоим подходом я не знаю как минимум по двум причинам: 1) не видно всего кода, а есть только кусок с удалением 2) не факт что я сам знаю правильных подход, но костыльность решения намекает что он может отличаться от твоего. Так понятно? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Пантер от Октябрь 10, 2012, 11:12 Я согласен с mutineer - код оставляет желать лучшего. Допустим, у меня в одной проге вот так сделано:
Код
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 10, 2012, 11:34 Я не думаю что ты докопался до истинной причины бага. Истинная причина бага - то что во время обработки readyRead сокет удаляется. А чтобы этого не происходило нужно исключить исполнение кода, который может выполнить такие действия(совершенно об этом не подозревая)PS. по поводу кода - мне он тоже не нравится. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 11:36 Я не думаю что ты докопался до истинной причины бага. Истинная причина бага - то что во время обработки readyRead сокет удаляется. А чтобы этого не происходило нужно исключить исполнение кода, который может выполнить такие действия(совершенно об этом не подозревая)Какая может быть обработка readyRead, если для удаления сокет должен перейти в disconnected состояние и никаких данных получать уже не может? Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 10, 2012, 11:52 Упс, только что заметил что в slotReceiveData вызывается slotDisconnectClient а не disconnectClient(отличные названия, блин). Если из slotDisconnectClient не вызывается disconnectClient то ниженаписанное можно не читать, а если вызывается, то:
в slotReceiveData() if (!result) this->slotDisconnectClient(clientNumber); а там deleteLatter для сокета(фактически внутри ReadyRead). теперь достаточно чтобы лишь дать выполниться этому делету, и всё - мы внутри readyRead удалённого сокета. Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 11:55 А зачем вообще вызывать удаление сокета из чтения данных?
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 10, 2012, 12:00 Спроси у автора. Но такая ситуация может возникныть и без ручного удаления. Если мы из readyread вызываем QCoreApplication::processEvents а в это время клиент дисконнектится, то получится то же самое.
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 12:05 Ну так значит сама идея вызова delete или processEvents из readyRead неправильна
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Patrin Andrey от Октябрь 10, 2012, 12:10 Так я о чём и говорю. Но как видим processEvent может вызваться из слота, который приконнекчен к сигналу, который эмитится в readyRead. А это уже контролировать очень сложно(можно конечно не эмитить сигналов:)). Поэтому если уж и эмитить сигналы из readyRead, то нужно обеспечить их присоединение через Qt::QueuedConnection.
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: mutineer от Октябрь 10, 2012, 12:12 Вот такую кашу вызовов неконтролируемо откуда я и имел в виду под неправильным подходом
Название: Re: Проблема с сервером (deleteLater() для сокета вызывает SegFault) Отправлено: Ground от Октябрь 10, 2012, 12:14 Вот такую кашу вызовов неконтролируемо откуда я и имел в виду под неправильным подходом Вот теперь все намного понятнее :) Но ей богу, пока сам на такие грабли не наступишь - нормально писать не станешь. |