Подскажите, есть ли способ обработать ошибку "segmentation fault"? Ошибка возникает при попытке удаления по адресу, который уже не принадлежит текущей thread.
catch это не отлавливает.
Или может быть есть способ определить, кому принадлежит конкретный адрес памяти - текущей thread или кому-то другому?
>Подскажите, есть ли способ обработать ошибку "segmentation fault"? Ошибка возникает при попытке
>удаления по адресу, который уже не принадлежит текущей thread.
>catch это не отлавливает.
>Или может быть есть способ определить, кому принадлежит конкретный адрес памяти -
>текущей thread или кому-то другому?Это территория gdb. Иногда стек бывает порчен, и узнать не удается ничего (особенно, если несколько потоков). Но если повезет.. Для некоторых систем код требуется собрать с опциями -g -fPIC. И уж точно отключит оптимизацию (-O0).
>(особенно, если несколько потоков). Но если повезет.. Для некоторых систем код
>требуется собрать с опциями -g -fPIC. И уж точно отключит оптимизацию (-O0).
Не получилось... на Linux FC4 с gcc, выдает все так же
*** glibc detected *** ./test: double free or corruption (fasttop): 0x08383288 ***
Но все равно спасибо.
Это даже без thread, чисто тест:
delete p;
cout << "delete 1" << endl;
try {
delete p;
}
catch (...) {
cout << "delete 2 error" << endl;
}
>Или может быть есть способ определить, кому принадлежит конкретный адрес памяти -
>текущей thread или кому-то другому?У потоков нет понятия принадлежности им адресов памяти.
Адресное пространство разделяется только между процессами.А за delete одного и того же объекта два раза надо отрывать руки. С корнем.
Если не согласны - Java или .NET вам в руки.
>А за delete одного и того же объекта два раза надо отрывать
>руки. С корнем.Проблема не в двойном удалении объекта, а в том что он удаляется потоком, которому он уже не принадлежит. В данном тесте попытка дважды удалить - это приблизительное воспроизведение ситуации.
>Проблема не в двойном удалении объекта, а в том что он удаляется
>потоком, которому он уже не принадлежит. В данном тесте попытка дважды
>удалить - это приблизительное воспроизведение ситуации.Еще раз. Нет такого понятия, как принадлежность объекта потоку (про thread local storage мы сейчас не говорим). Попытка дважды удалить один и тот же обьект - это недостаток проектирования. Если нечто подобное в программе ДЕЙСТВИТЕЛЬНО надо, то:
delete p;
p=NULL; // !!!!!!!!!!
cout << "delete 1" << endl;
...
// тут мы что-то делаем, причем далее не факт что когда-то уже был delete,
// поэтому надо его вызвать
try {
delete p; // delete NULL не имеет никакого эффекта, см. стандарт на язык C++
}
catch (...) {
cout << "delete 2 error" << endl;
}А за решение с catch я бы оторвал руки второй раз. Почему? Это тебе домашнее задание :)
>А за решение с catch я бы оторвал руки второй раз.Можешь и себе оторвать, и не только руки, но и язык :)
Еще раз объясняю ситуацию: работают несколько thread, каждая перед самым завершением вызывает delete 1 РАЗ. Пытаюсь разобраться в чем может быть причина того что время от времени (достаточно редко чтобы собрать статистику, раза 2-3) сервер вдруг падает. Каждый раз это происходит при большой загрузке сервера и в последние два раза удалось зафиксировать только то что у двух разных клиентов оказались в некоторый момент времени одинаковые адреса указателя на сокет (не сами дескрипторы!). В общем-то это странно, т.к. каждая thread создает свой экземпляр класса например A, который содержит этот самый указатель на сокет. Причем один клиент в этот момент начинал процедуру отката из-за временных проблем с сетью, а второй только пытался присоединиться, но при этом объект класса A уже создан.
Последовательность лога оказалась такая:
-первый получил некорректные данные из сети, собирается отключиться
-второй пытается присоединиться, сетевая ошибка, отсоединяется
-первый отсоединился
Это первое... А второе - прежде чем что-то сказать, читай теорию. close не закрывает сокет, а только уменьшает на единицу счетчик количества указателей на этот сокет. А вот если этот счетчик окажется равным 0, то тогда действительно закроет, а если еще не 0, то сокет будет продолжать работать.
>Это первое... А второе - прежде чем что-то сказать, читай теорию. close
>не закрывает сокет, а только уменьшает на единицу счетчик количества указателей
>на этот сокет. А вот если этот счетчик окажется равным 0,
>то тогда действительно закроет, а если еще не 0, то сокет
>будет продолжать работать.
Не на сокет, а на объект в ядре. Если допустим один процесс открыл файл, а другой этот файл удалил, то у первого ничего ужасного не произойдет, файл все равно открыт и связан с сокетом, а вот когда он его закроет - файл будет удален. Иными словами если сокет закрыт в процессе с помощью close() - дескриптор уже ни на что не указывает.
>Если допустим один процесс
>открыл файл, а другой этот файл удалил, то у первого ничего
>ужасного не произойдет, файл все равно открыт и связан с сокетом,
>а вот когда он его закроет - файл будет удален. Иными
>словами если сокет закрыт в процессе с помощью close() - дескриптор
>уже ни на что не указывает.Да, я понимаю что close сам по себе большой роли не сыграет. Но что произойдет если объект класса Socket (в нем не только дескриптор сокета) открыт через new, и thread имеет указатель *p на этот объект. Если предположить что в результате какого-то сбоя в памяти вторая thread получила свой *p по тому же адресу, то [delete p] во второй thread получается пытается удалить общий на данный момент участок, в том числе и сокет, который еще использует первая thread. Хотя конечно это только гипотеза, а в log не оказалось сообщения об ошибке, несмотря на запуск ... 1>>log 2>>log
>у двух разных клиентов оказались в некоторый момент времени одинаковые адреса указателяВот это и есть ошибка. И решать ее надо совсем не catch'ем.
>close
>не закрывает сокет, а только уменьшает на единицу счетчик количества указателей
>на этот сокет. А вот если этот счетчик окажется равным 0,
>то тогда действительно закроет, а если еще не 0, то сокет
>будет продолжать работать.Даа? Ну, спасибо что просветил. А как увеличить "счетчик количества указателей"? Именно это ты, наверное, и делаешь? Ведь чтобы уменьшить два раза, надо сначала увеличить два раза...
>>у двух разных клиентов оказались в некоторый момент времени одинаковые адреса указателя
>Вот это и есть ошибка. И решать ее надо совсем не catch'ем.
Естественно ошибка, но run-time, а воспроизвести ее никак не удается, т.к. в нормальной ситуации адреса разные:
========================
class Socket { ...}class A {
....
Socket* socket_;
....
~A() {... delete socket_;};
}
========================
Socket* current_socket;void StartThread() {
создается объект класса A(current_socket)
..... обработка ......
}....
boost::thread_group threads;
boost::function0<void> f = &StartThread;
.....
while (...есть клиент...) {
Socket* p=new Socket(...);
current_socket = p;
threads.create(f);
}
....
Вот и получается, что если не воспроизвести, то либо проблема не в этом, либо это была проблема системы, а не сервера, тогда надо просто как-то учесть вероятность, что это опять произойдет.>Даа? Ну, спасибо что просветил. А как увеличить "счетчик количества указателей"? Именно
>это ты, наверное, и делаешь? Ведь чтобы уменьшить два раза, надо
>сначала увеличить два раза...
Y меня только гипотеза, что в какой-то момент система раз уж отвела один и тот же адрес под указатели из разных thread, то возможно выдала тот же дескриптор сокета для второй thread, и тогда счетчик мог увеличиться на 1.
>Вот и получается, что если не воспроизвести, то либо проблема не в
>этом, либо это была проблема системы, а не сервера, тогда надо
>просто как-то учесть вероятность, что это опять произойдет.Не, систему отбрасываем, железо тоже.
Ты наступил на классическую ситуацию, когда объект удаляется, а ссылка (указатель) на него продолжает использоваться. То что на том же месте оказался другой объект такого же типа - это нормально. Старый удалили, новый выделили, на том же месте, так часто бывает. Но то что старый указатель все еще используется - это ошибка, так не должно быть никогда.В приведенном тобой коде я вижу одну ошибку. Возможно это и есть причина (по-крайней мере, последствия могут быть именно такие как ты описал). Глобальная переменная current_socket, фактически передаваемая как параметр потоку. Смотри что будет в следующем сценарии:
Сначала выполняется это:
Socket* p=new Socket(...);
current_socket = p;
threads.create(f);Затем не факт что StartThread нового потока уже заработала. Особенно не факт - на однопроцессорной машине. Вполне может быть такое что время потока, делающего while не истекло, система не переключила процессор на новый поток, и основной поток пошел на новый шаг цикла (допустим что из-за большой нагрузке у нас уже есть еще один клиент):
Socket* p=new Socket(...);
current_socket = p;
threads.create(f);Что мы имеем после этого? Переменная current_socket переписана. Тот объект (Socket), который был создан на предыдущем шаге цикла, фактически потерян. А оба новосозданных потока будут работать С ОДНИМ И ТЕМ ЖЕ объектом Socket, созданным на втором шаге цикла.
Вот и проблема. Двойное его удаление - это лишь следствие.Я не работал с библиотекой boost. Там что, действительно нельзя передать параметр потоку при его создании? Ты бы решил проблему, избавившись от глобальной переменной.
>Я не работал с библиотекой boost. Там что, действительно нельзя передать параметр
>потоку при его создании?Нда... Посмотрел я на этот boost и на его реализацию потоков. Редкая гадость. Параметр там действительно нельзя передать. В качестве параметра потока (который есть во всех нормальных реализациях потоков) они уже используют указатель на некий внутренний объект thread_param. Что интересно, объект thread_param они создают как локальный в конструкторе thread. Видимо первый же запуск им показал что так делать не надо. И что вы думаете? Они ввели специальный мьютекс, который гарантирует что новый поток успеет считать данные из thread_param до того как произойдет выход из конструктора thread. Ректальная офтальмология, короче. Они не оставили интерфейса для передачи потоку своего отдельного параметра. Так что, если не хотите отказываться от boost, то придется делать подобную реализацию, чтобы гарантировать что переменная current_socket не будет переписана до того как новый поток ее прочитает.
>Нда... Посмотрел я на этот boost и на его реализацию потоков. Редкая
>гадость. Параметр там действительно нельзя передать. В качестве параметра потока (который
>есть во всех нормальных реализациях потоков) они уже используют указатель на
>некий внутренний объект thread_param. Что интересно, объект thread_param они создают как
>локальный в конструкторе thread. Видимо первый же запуск им показал что
>так делать не надо. И что вы думаете? Они ввели специальный
>мьютекс, который гарантирует что новый поток успеет считать данные из thread_param
>до того как произойдет выход из конструктора thread. Ректальная офтальмология, короче.
>Они не оставили интерфейса для передачи потоку своего отдельного параметра. Так
>что, если не хотите отказываться от boost, то придется делать подобную
>реализацию, чтобы гарантировать что переменная current_socket не будет переписана до того
>как новый поток ее прочитает.Хм... глупости пишете, IMHO.
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
#include <iostream>struct MyParam
{
int data_;MyParam(const MyParam& mp)
: data_(mp.data_)
{}
MyParam(int data)
: data_(data)
{}
};class MyThread
{
private:
MyParam mp_;public:
MyThread(const MyParam& mp)
: mp_(mp)
{}
void operator()()
{
boost::xtime xt;
boost::xtime_get(&xt, boost::TIME_UTC);
xt.sec += 10;boost::thread::sleep(xt);
std::cout << "Thread parameter: " << mp_.data_ << std::endl;
}
};int main(int argc, char* argv[])
{
std::cout << "Booting Masa-Dosa..." << std::endl;
MyThread myThread(MyParam(666));
boost::thread thrd(myThread);
thrd.join();
return 0;
}
>
>Хм... глупости пишете, IMHO.Мобыть... Смотрел бегло.
Раз так, то можно пользоваться и избавиться от глобальной переменной.
> MyThread myThread(MyParam(666));
> boost::thread thrd(myThread);Интересно, действительно можно от глобальной наконец избавиться.
>Я не работал с библиотекой boost. Там что, действительно нельзя передать параметр
>потоку при его создании?
А почему бы не передавать параметр не потоку, а конструктору при создании нового экземпляра Socket?
>А почему бы не передавать параметр не потоку, а конструктору при создании
>нового экземпляра Socket?Переписывать больше :)
>А почему бы не передавать параметр не потоку, а конструктору при создании
>нового экземпляра Socket?
Не совсем понятно. Кроме указателя на Socket никаких других параметров не было необходимости передавать.
>Y меня только гипотеза, что в какой-то момент система раз уж отвела
>один и тот же адрес под указатели из разных thread, то
>возможно выдала тот же дескриптор сокета для второй thread, и тогда
>счетчик мог увеличиться на 1.
Таблица дескрипторов на процесс одна и такого быть не может.
>Таблица дескрипторов на процесс одна и такого быть не может.
Да, с этим уже разобрались, спасибо за man