Есть ли у epoll проблемы с потокобезопасностью?

Это продолжение вопроса 14221339.

У меня есть пул потоков, который работает внутри цикла epoll_wait().

Внешний поток вызывает epoll_ctl() и добавляет сокет слушателя с

(EPOLLET | EPOLLONESHOT | EPOLLIN).

Когда пул потоков имеет только один поток, он периодически не может получить событие EPOLLIN для первой (и единственной) попытки подключения. Если я увеличу пул потоков до двух, он почти всегда не сможет получить событие EPOLLIN.

Насколько я понимаю, API epoll является потокобезопасным, но это наблюдение, похоже, указывает на обратное.


person user1715664    schedule 09.01.2013    source источник


Ответы (1)


При семантике с запуском по фронту неправильная последовательность вызовов может привести к состоянию гонки. Задействованы три системных вызова:

  1. epoll_ctl() для активации уведомлений (и повторной активации, если используется EPOLLONESHOT).
  2. epoll_wait() для получения уведомлений.
  3. системный ввод: read()/recv()/accept() в цикле до ошибки EAGAIN.

При выполнении (неоднократно) в этом порядке возможна гонка между #3 и #1: когда входное событие в ядре происходит после возврата EAGAIN, но до того, как можно будет воздействовать на epoll_ctl(). Как правило, повторная активация должна выполняться перед вводом-выводом.

  1. epoll_ctl() для активации уведомлений (и повторной активации, если используется EPOLLONESHOT).
  2. системный ввод: read()/recv()/accept() в цикле до ошибки EAGAIN.
  3. epoll_wait() для получения уведомлений.

(Очевидно, что ввод-вывод должен быть неблокирующим.)

person arayq2    schedule 09.01.2013
comment
Я нашел проблему, и на самом деле она не имеет ничего общего с epoll. Мое приложение написано на C++, и создание пула потоков включает рекурсивную функцию шаблона с переменными аргументами и std::bind. Возникает какое-то странное состояние гонки, когда результирующий объект std::function очищается, и поэтому потоки никогда не входят в цикл epoll_wait(). - person user1715664; 09.01.2013
comment
@arayq2 Ты уверен в этом порядке звонков? Я никогда не видел, чтобы что-нибудь, связанное с epoll, вызывало их в таком порядке? У меня аналогичная проблема здесь. Страница руководства предполагает, что после повторного включения fd, если он может соответствовать критериям своих флагов, он вызовет событие, даже если ctl вызывается после ожидания. Любая дополнительная информация, которую вы можете дать по этому поводу, была бы здорово :) - person nathansizemore; 24.11.2015
comment
Я не уверен в том смысле, что могу цитировать откуда-то главу и стих. Тем не менее, состояние гонки довольно очевидно из существующей документации (которая тонкая, вздох), и на странице руководства epoll (7) говорится, что для EPOLLET вам нужны (1) неблокирующие fds и (2) ожидание только событий после операции ввода-вывода возвращается EAGAIN. Если вы не используете EPOLLONESHOT, вопрос о последующих вызовах epoll_ctl(... EPOLL_CTL_MOD..) не актуален; если вы действительно используете EPOLLONESHOT, проблема заключается в том, когда вызывать повторную активацию epoll_ctl() с учетом состояния гонки. - person arayq2; 25.11.2015
comment
Кроме того, если вы посмотрите на раннюю историю системы epoll (октябрь 2002 г. в списках рассылки ядра linux), станет ясно, что семантика граничных триггеров не совсем понятна. Ключевым моментом является то, что граничный триггер происходит при переходе состояния: из пустого в непустое для чтения и из полного в неполное для записи. Таким образом, чтобы включить следующий триггер, буферы сначала должны быть опустошены при чтении/заполнены при записи (т. е. EAGAIN). Проблемы достаточно тонкие, поэтому не следует ожидать, что один и тот же код будет работать без изменений с семантикой LT или ET. - person arayq2; 27.11.2015