Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Альтернативный заголовок: "В любой непонятной ситуации возвращай Out of memory".
Давеча решил я запустить свой самописный сервер веселья ради, как я делал это тысячу раз до этого, и каково же было моё удивление, когда я внезапно увидел следующую строчку в консоли: Error when parsing "example_proj.xml": 1:0 out of memory.
Для парсинга конфигурационных файлов в проекте используется сторонняя библиотека (назовём её LibCustomConfig), которая в свою очередь использует широко распространённую libexpat.
Итак. Out of memory? На XML в 50 строчек? Сказать, что я был ошарашен - это не сказать ничего. "Но ведь раньше всё работало". Ни код сервера, ни так называемый LibCustomConfig никак не менялись.
Будучи абсолютно сбитым с толку я решил свалить всю вину на последнее обновление последней Ubuntu, после которого проблема и появилась. А вместе с ней появились и другие проблемы: падения и подвисания разных приложений, как будто сам Chaos Monkey дотянулся до меня своими лапами. Может где-то в недрах поломался ABI?
Пересаживаюсь на свежую LTS версию Ubuntu, пересобираю сервер и... опять out of memory.
Пришло время расчехлять GDB. Кто-то скажет, что с этого и надо было начать, но дебажить сторонние либы очень не хотелось.
LibCustomConfig использует libexpat примерно так:
Parser::Parser(/*...,*/ char separator = ':')
{
if (separator)
/* Constructs a new parser and namespace processor. Element type
names and attribute names that belong to a namespace will be
expanded. */
parser_ = XML_ParserCreateNS(/*encoding = */ encoding_,
/*namespaceSeparator = */ separator);
else
{ /*...*/ }
}
Запоминаем, что сепаратор по умолчанию - ':' (корректно это или нет, я не знаю). Далее файл вычитывается в буфер и парсится стандартным методом из libexpat - XML_ParseBuffer:
XMLPARSEAPI(enum XML_Status)
XML_ParseBuffer(XML_Parser parser, int len, int isFinal);
При ошибке парсинга возвращается ненулевой статус, а код ошибки можно получить с помощью XML_GetErrorCode.
После получаса дебаггинга наконец нахожу, где же внутри libexpat фейлится парсинг (смотреть на GitHub):
Этот код был добавлен 12 февраля (коммит).
В качестве подопытного XML-файла используется просто <conf></conf>.
В данном случае parser->m_ns == 1 (root parser), uri == "http://www.w3.org/XML/1998/namespace" (автоматически устанавливается libexpat'ом), parser->m_namespaceSeparator == ':' (установлено в LibCustomConfig). Получается любой простой файл не будет парсится, если сепаратором указать двоеточие, т.к. этот символ присутствует в URI.
Но почему же я получаю Out of memory error, если возвращается XML_ERROR_SYNTAX?? Смотрим call stack:
сейчас мы находимся внутри функции addBinding, которая нам возвращает XML_ERROR_SYNTAX (смотреть на GitHub)
вызывается она внутри функции setContext, которая внезапно возвращает bool. В данном случае XML_False (смотреть на GitHub)
далее startParsing, которая возвращает то же булевское значение (смотреть на GitHub)
а затем знакомая XML_ParseBuffer, которая по возвращенному XML_False должна понять, что же произошло (смотреть на GitHub)
И как же XML_ParseBuffer понимает, что произошло? А очень просто:
if (parser->m_parentParser == NULL && ! startParsing(parser)) {
parser->m_errorCode = XML_ERROR_NO_MEMORY;
return XML_STATUS_ERROR;
}
В любой непонятной ситуации возвращай XML_ERROR_NO_MEMORY. Прикольно.