Pull to refresh

История одного бага или почему следует знать не только РНР

Reading time3 min
Views2.3K
Все началось с того, что стал падать мемкеш, вернее мемкешДб. И падал он как-то хитро. Переустановка мемкеш+мемкешДб+BerkeleyDb ничего не дала. После некоторых эмперических вычислений стало понятно, что падает на методе MultiGet, при том очень интересен тот факт, что падение зависит от порядка задания ключей и кол-во ключей должно быть более 3х.


Ошибка падения: #16 — неполный прием данных. Ага, значить барахлит мемкеш. Лезем в логи, анализируем логи на лету и видим заветное END — значить контент по логам отдается весь. Здесь следует раскрыть некоторые детали протокола обмена.
Существует два типа протокола обмена: бинарный и текстовый. В соответствии с текстовым протоколом сообщение состоит и
заголовка и собственно тела. Рассмотрим две основные команды, которые могут всем понадобиться при отладке: set и get.

set <имя ключа>  [noreply]\r\n
.... тело сообщения длинной length байт

если не установлен параметр noreply, то вернется STORED\r\n или ERROR\r\n
flags - флаги управления
expiraton - время, до которого кеш актуален
length - длинна
далее идет сообщения длинной length байт

get <имяключа>


должны получить строку:VALUE <имя ключа> .... тело сообщения длинной length байт
END

в случае мультигет:

gets <имяключа1> <имяключа2> <имяключа3> ...


должны получить строки:VALUE \r\n
.... тело сообщения длинной length байт
VALUE \r\n
.... тело сообщения длинной length байт
VALUE \r\n
.... тело сообщения длинной length байт
END

Более подробно в файле protokol.txt папки doc дистрибутива memcached

пробуем отладку из консоли:
telnet localhost 11211


через раз выдает то полный контент (заветный END), то останавливается почти у конца. Вопрос со времен Чернышеского: "Что делать?". Однако мой глаз падает на результаты вывода. Лезут сплошные кроказаблы - и не удивительно, ведь в мемкеши данные хранятся в зазиповонном формате. Очевидно, один из символов просто воспринимался терминалом как служебный и терминал подивсал. По логам - всё Ok!

Попытка #2. Итак повторю, наша цель определить что багует. А баговать может:
  • memcached
  • BerkeleyDb ( как составная часть memcachedb )
  • libmemcache (c-клиент)
  • memcached pecl расширение PHP.

Пишем PHP программу из 10 строчек, которая реализует упрощенный нативный memcached протокол:
- установить соединение
- записать в сокет строку: "gets <имяключа1> <имяключа2> <имяключа3>"
- прочитать строку (до символов \r\n)
- ответ вывести на экран, распарсить, получить длинну сообщения
- пропустить n-байт
- перейти к пункту 3.

Результат вывода программы приблизительно следующий:
VALUE key_1 67 678 178468
VALUE key_2 67 526 178468
VALUE key_3 67 349 178468
END


Проверяем на 1000 случайных ключей (уже сгенерированных данных и записанных в кеш)
Ага, все в порядке, значит из списка уже выпадает два пункта.

Попытка #3.
Пересобираем memcached экстеншен. Безрезультатно. Следует заметить, что она была пропатчена и поэтому можно было грешить на патч. Остается последовательно копать. Попробуем поискать в libmemcached.

Попытка #4.
Переустанавливаем libmemcached. Берем из каталога test тестовый пример и переписываем его на мультигет. Компилим с опцией включенной отладки и запускаем. Наблюдаем Sigmentation fault. Падает в методе формирования хешей. Заходим на офф. сайт, читаем багреппорты, качаем новую версию libmemcached, переустанавливаем, пересобираем РНР и счастливы...

Оказывается, сообщение об ошибке может содержать совсем иные мотивы и чтоб устранить простой баг мало быть просто РНР программистом... А в прочем, может Хабражители найдут еще и пару иных полезных мыслей.
Tags:
Hubs:
Total votes 14: ↑7 and ↓70
Comments5

Articles