У нас есть приложение, которое использует epoll для прослушивания и обработки http-соединений. Иногда epoll_wait () получает событие close на fd дважды в «строке». Значение: epoll_wait () возвращает соединение fd, по которому read () / recv () возвращает 0. Это проблема, поскольку у меня есть указатель malloc: ed, сохраненный в структуре epoll_event (struct epoll_event.data.ptr) и освобождаемый при fd (гнездо) определяется как закрытый в первый раз. Второй раз он падает.
Эта проблема возникает очень редко при реальном использовании (за исключением одного сайта, который на самом деле имеет около 500-1000 пользователей на сервер). Я могу воспроизвести проблему, используя http siege с> 1000 одновременных подключений в секунду. В этом случае ошибки приложения (из-за неверного указателя) происходят очень случайно, иногда через несколько секунд, обычно через десятки минут. Мне удалось воспроизвести проблему с меньшим количеством соединений в секунду, но для этого мне пришлось запускать приложение долго, много дней, даже недель.
Все новые соединения accept () fd: s устанавливаются как неблокирующие и добавляются в epoll как однократный запуск, запуск по фронту и ожидание доступности read (). Так почему же, когда нагрузка на сервер высока, epoll считает, что мое приложение не получило событие close и ставит новое в очередь?
epoll_wait () работает в своем собственном потоке и ставит в очередь события fd для обработки в другом месте. Я заметил, что было несколько закрытий, поступающих с простым кодом, который проверяет, происходит ли событие два раза подряд от epoll к одному и тому же fd Это случилось, и события, когда оба закрываются (recv (.., MSG_PEEK), сказали мне это:)).
Создан epoll fd:
epoll_create(1024);
epoll_wait () запускается следующим образом:
epoll_wait(epoll_fd, events, 256, 300);
новый fd устанавливается как неблокирующий после accept ():
int flags = fcntl(fd, F_GETFL, 0);
err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
новый fd добавлен в epoll (клиент имеет malloc: указатель структуры ed):
static struct epoll_event ev;
ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
ev.data.ptr = client;
err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client->fd, &ev);
И после получения и обработки данных с fd, он перезагружается (конечно, начиная с EPOLLONESHOT). Поначалу я не использовал io с триггерным фронтом и неблокирующим интерфейсом, но я протестировал его и получил хороший прирост производительности, используя их. Эта проблема существовала до их добавления. Btw. shutdown (fd, SHUT_RDWR) используется в других потоках для запуска надлежащего события закрытия, которое будет получено через epoll, когда серверу необходимо закрыть fd из-за какой-либо http-ошибки и т. д. (я не знаю, является ли это правильным способом сделай это, но сработало отлично).