Pull to refresh

select, сокеты и большое количество клиентов

В начале была проблема...


На работе поддерживаю и разрабатываю приложение под Линукс, которое раздает клиентам некий контент. В целом работа каждого клиента выглядит обычно — подключился по TCP/IP и обменивается пакетами с текстовой информацией пока дисконнект не разлучит их. Сервер с своей стороны на каждого клиента создает тред и цикле вызывает select. Пока клиентом было немного — всё работало достаточно стабильно и красиво. Но клиентов становилось больше и было замечено, что количество активных клиентов на сервер застрягло в районе 500-520. При этом нагрузки на сервере особой не было. Увеличение количества серверов давало немного времени, но ведь это не может быть бесконечным. Паралельно была замечена другая проблема — в логе часто начала появляться ошибка "Bad file description" (он же EBADF) и клиента отключало, а они жаловались. Причины подобной ошибки не непонятны. А это уже плохо.



Изучаем документацию


Исходя с документации, такая ошибка будет возникать, если дескриптор на момент обращения к нему (recv, send, select и так дальше) является невалидным. Например его кто то закрыл или благодаря неаккуратности программиста произошёл выход за пределы массива и в переменную, которая хранит дескриптор, был записан мусор. Но если почитать документацию ещё внимательней, то там есть интересная вещь — оказывается, select не может корректно работать с дескрипторами, чей номер больше 1024 (на самом деле это FD_SETSIZE, но в большинстве современных Linux он равен 1024), но многие мне доказывали, что select не может корректно работать, если количество дескрипторов, которые он "селектит" больше 1024. В манах этот факт либо умалчивается, либо написан очень расплывчато (а может виной мой не очень сильный английский...). По крайней мере, в man'ах debian, ubuntu и Fedora. В Suse есть заметка " An fd_set is a fixed size buffer. Executing FD_CLR or FD_SET with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior." То есть, не важно, сколько у вашего приложения открытых дескрипторов, важен номер. И после это всё стало на свои места — у меня на каждого клиента выделяется два дескриптора — один на сокет для клиента, другой для внутренних нужд. Эти два дескриптора в цикле и обслуживал select.



Опыты на гране UB


Паралельно выявил ещё одну интересную штуку. Если номер дескриптора больше чем количество разрешённых открытых файлов (это ulimit -n, обычно он равен также 1024). то select "зависает" на нем. (проверено на debian). Если в диапазоне FD_SETSIZE и ulimit -n, то стабильно дает Bad file description. Вполне возможно, что на других платформах поведение будет другим. Но на то оно и UB, что бы быть странным и не предсказуемым.



Идем в наступление на проблему


Были предложены следующие варианты выхода с сложившейся ситуации:


  • "подменить" FD_SETSIZE в приложении, указав число побольше. Обещают, что под FreeBSD это работает на ура. Но под Linux только добавляет глюков. Приложение начинает работать нестабильно, как только номер дескриптора добирается до 1024.
  • перекомпилировать ядро, указав нужное значение константы FD_SETSIZE. Уж слишком кардинальная мера, была оставленна на последок.
  • использовать poll вместо select.

Хороший poll


На то, что бы переписать приложение под использования poll вместо select, ушло около 4 часов.
Теперь код выглядит так.
struct pollfd fds[2];
int timeout_msecs = 100;
int ret;
//....
while (!m_terminated) {
fds[0].fd = m_fd1;
fds[0].events = POLLIN | POLLERR | POLLHUP;
fds[1].fd = m_fd2;
fds[1].events = POLLIN | POLLERR | POLLHUP;
ret = poll(fds, 2, timeout_msecs);
if (ret < 0 || errno == EAGAIN)
continue;
if (ret < 0) break;
if (ret > 0) {
//читаем+пишем в сокеты
}
}


Итого


Несколько недель наблюдений показало, что ошибка перестала возникать, стабильность улучшилась на порядок. Более того, количество клиентов на сервере поднялось почти в два раза. А в пиках достигало и трикратного прироста.
Начальство довольно, а я могу дальше продолжать доделывать мелкие баги…
Надеюсь заметка поможет и другим.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.