Различия в poll() между Linux и OS X, когда pollfd изменяется в другом потоке

Я пытаюсь запустить libwebsockets в многопоточной среде на OS X. Я не мог инициировать отправку данных из другого потока, отличного от основного потока службы. В документации по libwebsocket подразумевалось, что это должно быть возможно (демонстрационный код, список рассылки) . Поэтому я покопался в коде и нашел проблему в функции poll().

Кажется, что poll() ведет себя по-разному в отношении struct pollfd, заданного в качестве параметра. libwebsockets полагается на возможность изменять поля fds.event, когда poll() активен. Это нормально работает в Linux, но не работает в OS X.

Я написал небольшую тестовую программу, чтобы продемонстрировать поведение:

#include <unistd.h>
#include <netdb.h>
#include <poll.h>
#include <iostream>
#include <thread>

#define PORT "3490"

struct pollfd    fds[1];
bool connected = false;

void main_loop() {
    int sockfd, new_fd; 
    struct addrinfo hints, *servinfo, *p;
    socklen_t sin_size;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; 

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return;
    }

    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo);

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, 10) == -1) {
        perror("listen");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    new_fd = accept(sockfd, NULL, &sin_size);
    if (new_fd == -1) {
        perror("accept");
        return;
    }

    fds[0].fd = new_fd;
    fds[0].events = POLLIN;
    connected = true;

    printf("event is %i\n", fds[0].events);
    int ret = poll(fds, 1, 5000);
    printf("event is %i\n", fds[0].events); //expecting 1 on Mac and 5 on Linux

    if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
        perror("send");

    close(new_fd); 
    close(sockfd);
}

void second_thread()
{
    while(connected == false){}
    sleep(1);
    fds[0].events = POLLIN|POLLOUT;
    printf("set event to %i\n", fds[0].events);
}

int main() {

    std::thread t1(main_loop);
    std::thread t2(second_thread);

    t1.join();
    t2.join();

    return 0;
}

Скомпилируйте в OS X, используя clang++ -std=c++11 -stdlib=libc++ -o poll poll.cpp, и в Linux, используя g++ -std=c++11 -pthread -o poll poll.cpp

Программа начинает прослушивать порт 3490. Если вы подключитесь к ней (например, используя netcat localhost 3490), она будет опрашивать входные данные в основном потоке и пытаться изменить флаги событий во втором потоке. Он выйдет через 5 секунд.

Вывод на OS X:

server: waiting for connections...
event is 1
set event to 5
event is 1

Вывод в Linux:

server: waiting for connections...
event is 1
set event to 5
event is 5

Итак, мой вопрос: есть ли какая-либо доступная документация, объясняющая такое поведение? Безопасно ли то, что делает libwebsockets, ожидая, что законно изменить fds.events, пока опрос активен? Я не смог найти никаких подробностей об этом на справочных страницах (OS X, Linux).


person heine    schedule 24.08.2015    source источник
comment
Поскольку барьеров памяти нет, может ли это [просто] быть разницами в видимости потоков? (Если это так, я бы не решился считать любое поведение «четко определенным».)   -  person user2864740    schedule 25.08.2015
comment
опрос, выбор или события   -  person Elliott Frisch    schedule 25.08.2015
comment
@ user2864740 Я тоже об этом думал. Но, по крайней мере, изменение другой переменной в одном потоке и печать ее в другом отлично работает в обеих ОС.   -  person heine    schedule 25.08.2015
comment
Использование одной и той же переменной в двух потоках без мьютекса вызывает проблемы.   -  person Barmar    schedule 25.08.2015
comment
@ElliottFrisch Я видел эту страницу раньше, но только что заметил это предложение: poll() не уничтожает входные данные. Таким образом, предположение о том, что libwebsockets работает, верно, а OS X ведет себя странно. Это правильный вывод?   -  person heine    schedule 25.08.2015


Ответы (1)


Сначала вы, кажется, говорите, что нашли документацию, в которой утверждается, что это поддерживается и определено поведение. Мне было бы любопытно узнать, где вы это прочитали, потому что я не могу найти ничего ни на странице руководства Linux для poll(2), ни в Справочная страница POSIX для poll(), которая документирует, что другой поток может фактически изменить значения в аргументе массива событий, который другой поток передал в poll(), и что изменения другого потока действительно вступают в силу в исходном потоке. poll(), независимо от каких-либо проблем, связанных с барьерами памяти и т.п.

Обе справочные страницы кажутся мне совершенно безмолвными по этому вопросу. Они не указывают, является ли это ожидаемым, поддерживаемым или определенным поведением; или это не поддерживаемое или определенное поведение.

Предположение, что другой поток может изменить параметры системного вызова, выданного другим потоком, после -- ПОСЛЕ -- другой поток уже вошел в системный вызов, выглядит скорее контринтуитивно для меня. Если это поддерживаемое поведение, я ожидаю, что оно будет явно задокументировано, и я не могу найти никаких ссылок на него в справочных страницах Linux или POSIX.

Сказав это: даже если я ограничу область своего программного обеспечения Linux, даже если мне не нужно заботиться о других платформах; учитывая отсутствие какой-либо документации по этому поводу, и даже если бы мое тестирование показало, что ядро ​​Linux реализует poll(2) таким образом, я бы не рассчитывал на какие-либо гарантии того, что какая-то будущая версия ядра продолжит вести себя таким образом. Я бы не смог положиться на такое поведение, за исключением конкретной сборки ядра, с которой я это тестировал.

Итак, чтобы ответить на ваш вопрос: единственная авторитетная документация по этой теме - это рассматриваемые справочные страницы. Они явно не документируют это как законное поведение; и хотя они явно не говорят, что это незаконное поведение, по причинам, изложенным выше, я бы считал это неподдерживаемым, неопределенным поведением.

person Sam Varshavchik    schedule 24.08.2015
comment
Возможно, я немного неточно выразился в своем вопросе. Первая часть относится к libwebsocket. В демонстрационном коде и списках рассылки есть несколько точек, где автор говорит, что безопасно отправлять данные из другого потока (демонстрационный код, список рассылки). Но это приводит к поведению, которое я указываю в своем примере кода. Ваша публикация в основном поддерживает мое предположение о неопределенном поведении. - person heine; 25.08.2015
comment
Правильный. Заявление о том, что это задокументированное юридическое поведение, явно неверно. Ни в справочных страницах Linux, ни в Unix нет ничего, что явно документировало бы это как определенное поведение с ожидаемыми результатами. Аргумент должен быть сведен к ну, явно не задокументировано, что это не работает, поэтому это должно быть разрешено. Каждый волен принимать собственные решения, но я бы не стал основывать свой собственный код на таком предположении. P.S. Я не вижу ничего в сообщении связанного списка рассылки, в котором утверждалось бы, что это злоупотребление poll() действительно. - person Sam Varshavchik; 25.08.2015
comment
Он не утверждает этого прямо. Цитата: Это - само по себе - должно быть защищено от других контекстов потока. что относится к вызову libwebsocket_callback_on_writable(), который манипулирует fds.events, когда он может использоваться poll() в другом потоке. Поскольку сам libwebsocket не претендует на потокобезопасность, но подразумевает, что он может работать, если вы это сделаете, и что это, вероятно, нормально. Итак, я думаю, я нашел случай, когда он вообще не работает, и, как вы сказали, он может сломаться, если реализация poll() в Linux изменится, поскольку это неопределенное поведение, на которое вы не должны полагаться. - person heine; 25.08.2015