Anatoly Vorobey (avva) wrote,
Anatoly Vorobey
avva

рабочее

Вот тут написано, чем я занимался в основном последние неделю-две.

Сегодня был забавный баг:

mod_rewrite - это такой стандартный модуль для вебсервера (apache), позволяющий менять URLи, задавая всякие сложные правила. В частности, он поддерживает переписывание URLей внешней программой: apache её запускает, и все процессы-дети, обрабатывающие запросы, говорят с ней через stdin/stdout (сериализируя свой доступ через lockfile): посылают ей строку URLя, пришедшую от клиента, и получают обратно новую. Я написал такую внешнюю программу-сервер для нашей новой системы load balancing: переписывание в данном случае заключается в подставлении нужного сервера. Вот как это выглядит в общих чертах: HTTP-запрос приходит на внешний load balancer и отправляется им на одну из машин-прокси. На этих машинах бегут mod_rewrite и mod_proxy: mod_rewrite посылает моему серверу URL, скажем, "users/avva/friends/"; мой сервер находит относительно свободный веб-сервер во внутренней сетке, скажем, 10.0.0.2, и выдаёт обратно строку "http://10.0.0.2/users/avva/friends/"; этот переписанный URL переходит к mod_proxy, которая работает как прокси, пересылает запрос юзера этому вебсерверу, получает ответ и пересылает его юзеру.

(есть причины, по которым выгодно работать по такой двух-уровневой схеме, через внутренние прокси - не буду сейчас вдаваться в них)

Мой сервер читает запросы со своего stdin и отвечает на них на stdout, и одновременно слушает на определённом UDP-порте; а все веб-серверы постоянно посылают сообщения broadcast'ом на этот порт о том, сколько на них есть свободных детей для обработки запросов (отсылкой статистики занимается специальный модуль, к-й я написал на Перле, он бежит внутри mod_perl на веб-серверах). Сначала этот сервер был написан на Перле, но он не справлялся просто с потоком этой статистики и одновременно с запросами от mod_rewrite по stdin/stdout; поэтому я переписал его на C, и сейчас работает очень хорошо. Но вот какой был баг. Главный цикл моего сервера - select() на два дескриптора: UDP-socket'а и stdin; когда на одном из дескрипторов есть новые данные, он читает их и обрабатывает. Если эти данные приходят из stdin, он ожидает увидеть там целую строку (URL плюс \n, завершающий её). Код сервера делает read() с дескриптора stdin до тех пор, пока есть что читать, так что если даже строка URLа разобьётся на несколько вызывов read(), он её правильно сложит вместе (это возможно, т.к. строки URL могут быть очень длинными в случае больших GET-запросов; некоторые сумасшедшие прокси и серверы переписывают POST-запросы как GET-запросы - насколько я понял, Hotmail грешит этим, в частности). Но вот если строка URL оказывалась разорванной между вызовами select(): т.е., например, пришли какие-то данные, он читает, читает, читает, получает в конце концов "users/avva/friends", но без \n в конце, и read() ему говорит, что читать больше нечего - и только после ещё одного вызова select() придут новые данные - вот в этом случае код не справлялся и вёл себя неправильно. Но казалось бы, как такое может произойти? - ведь модуль mod_rewrite пишет всю строку для внешнего сервера сразу, трудно представить, зачем бы он вёл себя иначе. Тем не менее это происходило, но довольно редко.

Оказалось вот что. mod_rewrite посылает данные в stdin моего сервера так (копирую из его исходников):

write(fpin, key, strlen(key));
write(fpin, "\n", 1);

Само по себе разделение этой записи на два вызова write() мне ничем не мешает, т.к. я всё равно вызываю read() столько раз, сколько нужно, соединяя результаты в одном буфере. Но если между этими двумя вызовами write() произойдёт task switch — именно в этом месте! — и процессор переключится на мой процесс, то я получу всю строку за исключением "\n", а остаток получу уже только после следующего вызова select(). Понятно теперь стало и то, почему это происходило, и то, почему происходило так редко. А если бы mod_rewrite объединял ключ (так URL называется в терминологии этого модуля) с "\n" каким-нибудь несчастным sprintf'ом перед отсылкой в write(), никакой проблемы у меня бы не возникло, и не пришлось бы переписывать заново весь главный цикл прослушивания данных и обработки запросов с stdin. Но я не жалею, что пришлось, на самом деле. Просто забавно.
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 10 comments