LINUX.ORG.RU

Странный баг при работе с разделяемой памятью в C


0

0

Есть сервер и несколько клиентов. Сервер создает блок разделяемой памяти (примерно на 16Мб размером) и пишет туда какие то данные. Клиенты запускаются, подключают этот блок для чтения и читают их оттуда, после чего завершают работу. Клиенты, прошу заметить, на PHP, используют extension на C, который собственно и занимается подключением и чтением этого блока разделяемой памяти. Все вроде бы работает хорошо и прекрасно, однако периодически (примерно в 5-10% случаев) при исполнении клиента, происходит совершенно странная дикость (или дикая странность, как угодно). Блок памяти подключается на ура (ошибок не возвращает, адрес сегмента, отображенного в память процесса, похож на правду). Однако в нем одни нули. Т.е. никаких данных нет, одни нули. Только в конце блока есть немного мусора (начиная со смещения F0F008). Для того чтобы от сего неприятного явления избавиться, достаточно запустить клиент заново, и как правило он подключает и читает блок нормально. Я честно говоря в ступоре, никаких идей даже нет.

Если у кого нибудь есть идеи, с чем сие связано и как лечить, буду очень рад )

Ответ на: комментарий от mephisto123

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

Попробуй http://log4c.sourceforge.net/ Я не знаю, как оно хорошо повторяет log4j, у которого идея хорошая. Ну или сразу в syslog.

mv ★★★★★
()
Ответ на: комментарий от mephisto123

> Да я вобщем то тоже так думаю, но как выловить и почему он неправильный.

Логгирование всего и вся, чтобы потом можно было восстановить и проанализировать ход работы программы.

mv ★★★★★
()
Ответ на: комментарий от mv

Логгировать и привык, только интересует рекомендуемый способ) попробую log4c.

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

> так, с этого места поподробнее) где shmem не обнуляется?

Пост mky:
http://www.linux.org.ru/view-message.jsp?msgid=2987181#2989363

Далее, mephisto123:
http://www.linux.org.ru/view-message.jsp?msgid=2987181#2989414

    result=shmdt(shmem);
    if (result<0) return -1; <- вот тут оно не обнулится
    shmem=NULL;

Еще, mephisto123:
http://www.linux.org.ru/view-message.jsp?msgid=2987181#2988976

result=SM_InitIPC(0); // подключаем блок 
   if (result) RETURN_LONG(-2); <- вот тут оно не обнулится,
                                   т.к. до SM_FinishIPC не дойдёт

Die-Hard ★★★★★
()
Ответ на: комментарий от mephisto123

> Ничего страшного тут не будет, ...

Вот тут будет:
*map=players[i].map;
*location=players[i].location;
*x=players[i].x;
*y=players[i].y;

По ходу всех этих присваиваний сервер
может много раз поменять данные.

> никакого затирания всей памяти тут все
равно не будет )

Да, твой баг не про то.
Но даже если ты его отловишь,
оно правильно работать не будет.

Переводить игрока в оффлайн, менять данные
в оффлайне и потом включать игрока тоже не
выход: ты проверяешь, что ты онлайн, начинаешь
присваивания, а в это время сервер тебя
переводит в оффлайн и бодро эти данные меняет.

Die-Hard ★★★★★
()
Ответ на: комментарий от mephisto123

> ...отваливаются с segmentation fault. как бы это выловить...

Проще всего так: Компилишь с отладкой, генеришь core файл, смотришь его gdb.

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

Разумно конечно, но делать тотальные блокировки, заставляющие сервер большую часть времени тупо ждать пока клиенты считают данные (а клиентов будет много одновременно ломиться) мне бы крайне не хотелось. Если сервер и поменяет данные клиента во время чтения, то самые неприятные последствия, с которыми мы столкнемся - лишенные смысла данные о положении игрока, что само по себе не так критично и страшно. Более того, по логике программы одновременно map location x и y будут меняться очень редко (при переходе игрока между картами). Так что я думаю, что вероятность в 0.1% получения левых данных о положении игрока - наименьшее зло по сравнению с сервером, который не может выполнить все полагающиеся операции с игроками и объектами на карте вовремя по причине постоянной блокировки данных.

> result=SM_InitIPC(0); // подключаем блок > if (result) RETURN_LONG(-2); <- вот тут оно не обнулится, > т.к. до SM_FinishIPC не дойдёт

на самом деле если SM_InitIPC возвращает ошибку, то скрипт сразу выдает ошибку и завершает работу. использовать shmem он дальше не будет 100%. Более того, в указанном примере SM_InitIPC может вернуть ошибку лишь в двух случаях - либо shmat вернул ошибку, либо shmget вернул ошибку. В любом из этих двух случаев shmem не будет затронут и в нем останется NULL.

В SM_FinishIPC и правда нужно обнуление shmem вверх передвинуть.

mephisto123
() автор топика
Ответ на: комментарий от Die-Hard

> Проще всего так: Компилишь с отладкой, генеришь core файл, смотришь его gdb.

Извиняюсь за нубство опять таки, как сгенерить core и что есть gdb (ну понятно, что прога, но какую инфу оно даст?) Хотя ладно, ман почитаю по gdb )

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

> Разумно конечно, но делать тотальные блокировки, заставляющие сервер большую часть времени тупо ждать пока клиенты считают данные

Давно всё уже изобретено: read_lock, write_lock. Или даже read_copy_update. Блокировки нужны.

mv ★★★★★
()
Ответ на: комментарий от mephisto123

gdb -- отладчик.

Компилишь программу с флагами -g -O0

Разрешаешь генерацию коры (если под башем, то ulimit -c unlimited _в_ _той_ _оболочке_, которая запустит программу)

После сегфолта получишь файл core (может, с присобаченным ПИДом)

Говоришь:
gdb <имя бинарника> <имя core файла>

Оно тебе скажет строчку, на которой грохнулось.
Далее говоришь:
where
и оно покзывает место в файле.


Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

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

скомпилить пхп с такими опциями для меня тоже задача нетривиальная, но нарно разберусь ))

насчет блокировок. пусть даже это read_lock, серверу все равно придется ждать всех клиентов чтобы сделать write_lock.

mephisto123
() автор топика
Ответ на: комментарий от mv

И правда RCU помогло бы сильно. Проблема в том что сейчас структура данных совсем не подходит для этого. Данные тут одним большим блоком, соответственно с указателями так не пооперируешь. Нужно сильно менять код чтобы применить это. Пока отложу, главное критические баги пофиксить, потом можно будет и усовершенствованиями заняться.

А куда оно core-файл положит ?

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

> Проблема в том что сейчас структура данных совсем не подходит для этого. Данные тут одним большим блоком, соответственно с указателями так не пооперируешь.

В твоем случае можно сделать все гораздо проще: тебе не надо wait-free (более того, тебе достаточно отвалить на данный цикл от апдейта).

Идея такова (оно совершенно не годится для "общеупотребимых" блокировок, но в твоем случае проканает):

Разрешаешь на запись в сегмент клиенту.

Помимо поля "игрок оффлайн" добавить поле "игрок читает".

Север:

Выставляет поле "игрок оффлайн".

Проверяет поле "игрок читает". Если выставлено -- восстанавливает (если изменил) "игрок оффлайн" и отваливает до следующего цикла. Иначе:

Апдейтит данные.

Восстанавливает поле "игрок оффлайн".

Клиент:

Выставляет поле "игрок читает".

Проверяет "игрок оффлайн". Если он оффлайн, убирает "игрок читает" и отваливает до следующего цикла. Иначе:

Читает данные

Сбрасывает поле "игрок читает".

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

Идея в принципе интересная. В любом случае сначала разберусь с сегментейшн фаултами.

mephisto123
() автор топика
Ответ на: комментарий от mv

эм на тестовой машинке нет даже папки /etc/security. слака 12.0 стоит. на реальном сервере файл наличествует.

какой лимит надо ставить, soft или hard? ставить в 0 или большое число ?

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

hard ограничивает планку сверху для soft (больше, чем hard, уже не поставишь). Ставь unlimited, хоть soft, хоть hard.

mv ★★★★★
()
Ответ на: комментарий от mephisto123

> Или нужно из той оболочки, которая запускает апач ?

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

Die-Hard ★★★★★
()
Ответ на: комментарий от mephisto123

> какой лимит надо ставить, soft или hard? ставить
в 0 или большое число ?

man bash

Дальше нажимаешь слэш (/) и вбиваешь:
ulimit
Топчешь Enter и читаешь.

Die-Hard ★★★★★
()

ребят, сорри что встреваю :). а valgrind уже пробовали? :).

true_admin ★★★★★
()

да, я бы не стал из-под апача запускать. я бы просто попытался через php <имя скрипта>. Дебажить проще.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

да, только баг случается рандомно и более того, не у меня... а у других юзеров на live сервере. поэтому ловить нужно именно под апачем. в том и трабла.

valgrind не пробовал. опять таки ввиду вышеописанного стандартные методы не подходят.

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

Segmentation fault-ы вроде выловил, баг хитрый был ) правда к сабжу отношения не имел. Сабж пока не уверен, случается или нет, потестю.

mephisto123
() автор топика
Ответ на: комментарий от Die-Hard

>Идея такова (оно совершенно не годится для "общеупотребимых" блокировок, но в твоем случае проканает):

>Разрешаешь на запись в сегмент клиенту.

ИМХО, можно проще. Просто сделать, чтобы сервер не использовал структуры пользователей, которые ушли в off-line менее, чем 10 сек назад (по полю "players[i].lastrefresh"). Это как бы совсем не блокировка, но в данной ситуации, ИМХО, минимум правки кода, а сервер, скорее всего не настолько загружен, чтобы выполнение скрипта прерывалось на 10 секунд реального времени...

Ну, а если все разом выйдут и потом не смогут зайти в течении 10 секунд, это не страшно...

>проц старенький, на домашнем серваке тестирую, Celeron Tualatin 1300

>баг случается рандомно и более того, не у меня... а у других юзеров на live сервере

Это про один сервер? Или баг случается только на многопроцессорной машине?

mky ★★★★★
()
Ответ на: комментарий от mky

Сабж случается на обеих машинах, но сейчас очень редко, ввиду описанного на первой странице костыля (пытаемся получить координаты игрока 5 раз подряд с задержкой 10мс между попытками, вместо того чтобы сразу вываливаться если не получилось).

Однако все же случается. Лично я не ловил, но игроки изредка ловят.

Вообще хотелось бы поблагодарить всех участников беседы за конструктивные замечания и советы :) Очень помогло.

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

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

Хотелось бы увидеть код, который бы подтвердил ваши слова:

>Блок памяти подключается на ура (ошибок не возвращает, адрес сегмента, отображенного в память процесса, похож на правду). Однако в нем одни нули. Т.е. никаких данных нет, одни нули.

Вы говорили, что делали дамп памяти в файл, можно увидеть данный код? Может вывод в файл с ошибкой, а проблемма из-за неправильного значения players[i].lastrefresh ?

Если включить логгирование, то часто ли не срабатывает первая попытка?

И заодно посмотрите вывод команды ipcs, сколько у вас сегментов памяти и счетчик nattch для каждого, может там что страшное вылезет...

mky ★★★★★
()
Ответ на: комментарий от mky

0x1983267a 196610     nobody    644        16777216   12

ничего страшного вроде бы )

по поводу кода. щас попробую по памяти, стер уже.

   curtime=time(NULL);
   for (i=0;i<DYNAMIC_LIST_ENTRIES;i++) {
      if (players[i].name[0]&&(players[i].pid==id)&&((curtime-players[i].lastrefresh)<=R
EFRESH_TIMEOUT)) break;
   }
   if (i==DYNAMIC_LIST_ENTRIES) {
      f=fopen(DUMP_PATH,"wb");
      fwrite(zonebase,ZONE_SIZE,1,f);
      fclose(f);
      return -2;
   }
   
проще некуда вроде бы)

логгирование подробное сделал, на чем и выловил сег фаулт.

насчет сабжа. сейчас посмотрел по логам, вроде бы не нашел описанной ситуации. 
Т.е. возможно игроки ловили такие сообщения по другим причинам (например при попытке совершить какую либо операцию с игроком в оффлайне, 
такие ситуации пока не обрабатываются совсем корректно). 
Буду наблюдать )

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

Не нравится мне fwrite (личная неприязнь). Я бы сделал так, заодно по кол-ву и времени модификации файлов можно оценить частоту возникровения ошибки:

if (i==DYNAMIC_LIST_ENTRIES) {
   int fd;
   char fname[]="/tmp/shmem_XXXXXX";
   if ( (fd=mkstemp(fname)) != -1 ) {
       write(fd, zonebase, ZONE_SIZE);
       close(fd);
   }
   return -2;
}

Я пока пойду посплю, пишите, если что интерестное выяснится...

mky ★★★★★
()
Ответ на: комментарий от mky

в принципе интересная идея, но у меня уже 4 ночи, так что я тоже спать. завтра буду дальше копать )

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

> valgrind не пробовал. опять таки ввиду вышеописанного стандартные методы не подходят.

почему он не подходит? да, shmem не дебажит, зато обычную память проверит.

true_admin ★★★★★
()
Ответ на: комментарий от Deleted

memcached с сервером через сокет соединяется и по определению медленнее. более того, юникс сокеты не поддерживаются пхп-клиентом, а через tcp не хочется вдвойне.

mephisto123
() автор топика
Ответ на: комментарий от mephisto123

>memcached с сервером через сокет соединяется и по определению медленнее.

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

Deleted
()

2mephisto123:

Учвствую, проблему так и не поборол... Расскажи потом, в чем дело было, pls.!

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

А фиг его знает. Вроде не появляется больше.

Решил проблему описанным костылем.

mephisto123
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.