У нас есть приложение, которое использует epoll для прослушивания и обработки http-соединений. Иногда epoll_wait() получает закрытое событие на fd дважды в строке. Значение: epoll_wait() возвращает соединение fd, на которое read()/recv() возвращает 0. Это проблема, так как у меня есть malloc: ed pointer, сохраненный в epoll_event struct (struct epoll_event.data.ptr) и который освобождается, когда fd (сокет) определяется как закрытое в первый раз. Второй раз он падает.epoll_wait() получает сокет закрыт дважды (read()/recv() возвращает 0)
Эта проблема встречается очень редко в реальном использовании (кроме одного сайта, на котором фактически имеется около 500-1000 пользователей на сервер). Я могу реплицировать проблему, используя http siege с> 1000 одновременных подключений в секунду. В этом случае приложение segfaults (из-за недействительного указателя) происходит очень случайным образом, иногда через несколько секунд, обычно после нескольких десятков минут. Я смог реплицировать проблему с меньшим количеством подключений в секунду, но для этого мне нужно запускать приложение долгое время, много дней, даже недель.
Все новые соединения accept() fd: s устанавливаются как неблокирующие и добавляются в epoll как одноразовые, с кратковременным запуском и ждут, когда read() будет доступен. Так что, когда загрузка сервера высока, epoll считает, что мое приложение не получило закрытие и очередность нового?
epoll_wait() работает в своем потоке и ставит в очередь события fd, которые нужно обрабатывать в другом месте. Я заметил, что было несколько закрытий, входящих с простым кодом, который проверяет, происходит ли событие дважды в строке от epoll до того же fd. Это произошло, и события, когда оба закрываются (recv (.., MSG_PEEK) сказали мне это :)).
Epoll ФД создано:
epoll_create(1024);
epoll_wait() выполняются следующим образом:
epoll_wait(epoll_fd, events, 256, 300);
новых ФД устанавливаются как неблокирующие после принимать():
int flags = fcntl(fd, F_GETFL, 0); err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
новых ФД добавлен в epoll (клиент - malloc: ed struct pointer):
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-ошибки и т. д. (я действительно не знаю, правильно ли это сделайте это, но он отлично сработал).
Проблема при работе с valgrind заключается в том, что с этим огромным количеством соединений valgrind просто берет слишком много CPU. И даже при меньших объёмах соединения, похоже, valgrind/gdb приводит к тому, что проблема просто больше не возникает. Я не думаю, что проблема заключается в преждевременном освобождении. Я получаю события от epoll (он никогда не сбой там), приложение только падает, когда тот же fd получает несколько событий закрытия подряд: приложение пытается освободить один и тот же ресурс дважды. Я думаю, что этого никогда не должно было случиться, по крайней мере, с одноразовыми гнездами, срабатывающими по краю. –