2011-01-18 2 views
3

У нас есть приложение, которое использует 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-ошибки и т. д. (я действительно не знаю, правильно ли это сделайте это, но он отлично сработал).

ответ

1

epoll_wait() работает в своем собственном потоке и представляет собой очередные события fd, которые нужно обрабатывать в другом месте. ... Итак, почему, когда загрузка сервера высока, epoll считает, что мое приложение не получило закрытие события и очередей новых?

Предполагая, что EPOLLONESHOT это ошибка бесплатно (я не искал связанные с ошибками, хотя), тот факт, что вы обрабатываете ваш Epoll событие в другом потоке, и он выходит из строя спорадический или при большой нагрузке может означать, что есть условие гонки где-то в вашей заявке.

Возможно, объект, на который указывает epoll_event.data.ptr, досрочно освобождается до того, как событие epoll не зарегистрировано в другом потоке, когда сервер выполняет активное закрытие клиентского соединения.

Моя первая попытка состояла в том, чтобы запустить его под valgrind и посмотреть, не сообщает ли он какие-либо ошибки.

+0

Проблема при работе с valgrind заключается в том, что с этим огромным количеством соединений valgrind просто берет слишком много CPU. И даже при меньших объёмах соединения, похоже, valgrind/gdb приводит к тому, что проблема просто больше не возникает. Я не думаю, что проблема заключается в преждевременном освобождении. Я получаю события от epoll (он никогда не сбой там), приложение только падает, когда тот же fd получает несколько событий закрытия подряд: приложение пытается освободить один и тот же ресурс дважды. Я думаю, что этого никогда не должно было случиться, по крайней мере, с одноразовыми гнездами, срабатывающими по краю. –

0

я бы повторно проверить себя против следующих разделов из epoll(7):

Q6
Будет ли закрытие дескриптора файла причиной его удаления из всех наборов Epoll автоматически?

и

о При использовании кэш событий ...

Там уже несколько хороших точек существует.

0

Извлечение EPOLLONESHOT заставило проблему исчезнуть после нескольких других изменений. К сожалению, я не совсем уверен, что вызвало это. Использование EPOLLONESHOT с потоками и добавление fd снова вручную в очередь epoll было вполне определенно проблемой. Также указатель данных в epoll struct освобождается после задержки. Прекрасно работает.

3

Как только первый read() возвращает 0, это означает, что соединение было закрыто одноранговым узлом. Почему ядро ​​генерирует событие EPOLLIN для этого случая? Ну, нет другого способа указать закрытие сокета, когда вы только подписаны на EPOLLIN. Вы можете добавить EPOLLRDHUP, который в основном такой же, как проверка для чтения(), возвращающего 0. Однако не забудьте проверить этот флаг до, который вы тестируете для EPOLLIN.

if (flag & EPOLLRDHUP) { 
    /* Connection was closed. */ 
    deleteConnectionData(...); 
    close(fd); /* Will unregister yourself from epoll. */ 
    return; 
    } 

    if (flag & EPOLLIN) { 
    readData(...); 
    } 

    if (flag & EPOLLOUT) { 
    writeData(...); 
    } 

Как я уже заказал эти блоки актуальна и возвращение для EPOLLRDHUP важно также, потому что это, вероятно, что deleteConnectionData() могут быть разрушены внутренние структуры. Поскольку EPOLLIN установлен также в случае закрытия, это может привести к некоторым проблемам. Игнорирование EPOLLIN безопасно, так как оно не даст никаких данных. То же самое для EPOLLOUT, поскольку оно никогда не отправляется вместе с EPOLLRDHUP!

0

Зарегистрировать сигнал 0x2000 для удаленного подключения к удаленному хосту ex ev.events = EPOLLIN | EPOLLONESHOT | ЭПОЛЛЕТ | 0x2000 и проверьте, нет ли (флаг & 0x2000) для удаленного хоста Close Connection

Смежные вопросы