Асинхронная интеграция libcurl с kevent в macOS Sierra

Я интегрирую curl в цикл событий асинхронного ввода / вывода, основанный на kqueue.

libcurl имеет отличный API для интеграции в цикл событий приложений.

Вы предоставляете libcurl два обратных вызова: один для установки таймера (используется для ограничения времени запроса / подключения), а другой для регистрации файловых дескрипторов libcurl для событий чтения / записи / ошибки.

Документация для обратного вызова, используемого для регистрации FD, находится здесь: CURLMOPT_SOCKETFUNCTION

Аргумент, который сообщает обратному вызову о том, какие события интересует libcurl, имеет четыре значения enum:

CURL_POLL_IN

Wait for incoming data. For the socket to become readable.

CURL_POLL_OUT

Wait for outgoing data. For the socket to become writable.

CURL_POLL_INOUT

Wait for incoming and outgoing data. For the socket to become readable or writable.

CURL_POLL_REMOVE

The specified socket/file descriptor is no longer used by libcurl.

Хотя это явно не задокументировано, libcurl ожидает, что при последующих вызовах обратного вызова состояние фильтра цикла событий будет обновлено, чтобы соответствовать тому, что он передал. т.е. если при первом звонке это прошло CURL_POLL_IN (EVFILT_READ) и при последующем вызове он прошел CURL_POLL_OUT (EVFILT_WRITE), то оригинал EVFILT_READ фильтр будет удален.

Я обновил регистрационный код FD, чтобы справиться с этим.

int fr_event_fd_insert(fr_event_list_t *el, int fd,
               fr_event_fd_handler_t read,
               fr_event_fd_handler_t write,
               fr_event_fd_handler_t error,
               void *ctx)
{
    int       filter = 0;
    struct kevent evset[2];
    struct kevent *ev_p = evset;
    fr_event_fd_t *ef, find;

    if (!el) {
        fr_strerror_printf("Invalid argument: NULL event list");
        return -1;
    }

    if (!read && !write) {
        fr_strerror_printf("Invalid arguments: NULL read and write callbacks");
        return -1;
    }

    if (fd < 0) {
        fr_strerror_printf("Invalid arguments: Bad FD %i", fd);
        return -1;
    }

    if (el->exit) {
        fr_strerror_printf("Event loop exiting");
        return -1;
    }

    memset(&find, 0, sizeof(find));

    /*
     *  Get the existing fr_event_fd_t if it exists.
     */
    find.fd = fd;
    ef = rbtree_finddata(el->fds, &find);
    if (!ef) {
        ef = talloc_zero(el, fr_event_fd_t);
        if (!ef) {
            fr_strerror_printf("Out of memory");
            return -1;
        }
        talloc_set_destructor(ef, _fr_event_fd_free);
        el->num_fds++;
        ef->fd = fd;
        rbtree_insert(el->fds, ef);
    /*
     *  Existing filters will be overwritten if there's
     *  a new filter which takes their place.  If there
     *  is no new filter however, we need to delete the
     *  existing one.
     */
    } else {
        if (ef->read && !read) filter |= EVFILT_READ;
        if (ef->write && !write) filter |= EVFILT_WRITE;

        if (filter) {
            EV_SET(ev_p++, ef->fd, filter, EV_DELETE, 0, 0, 0);
            filter = 0;
        }

        /*
         *  I/O handler may delete an event, then
         *  re-add it.  To avoid deleting modified
         *  events we unset the do_delete flag.
         */
        ef->do_delete = false;
    }

    ef->ctx = ctx;

    if (read) {
        ef->read = read;
        filter |= EVFILT_READ;
    }

    if (write) {
        ef->write = write;
        filter |= EVFILT_WRITE;
    }
    ef->error = error;

    EV_SET(ev_p++, fd, filter, EV_ADD | EV_ENABLE, 0, 0, ef);
    if (kevent(el->kq, evset, ev_p - evset, NULL, 0, NULL) < 0) {
        fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno));
        talloc_free(ef);
        return -1;
    }
    ef->is_registered = true;

    return 0;
 }

К сожалению, это не работает. Похоже, что kevent не удаляет старые фильтры (мы продолжаем получать от них уведомления).

Что еще более странно, если применить две операции в двух отдельных вызовах, это работает отлично.

if (filter) {
    EV_SET(&evset, ef->fd, filter, EV_DELETE, 0, 0, 0);
    kevent(el->kq, evset, ev_p - evset, NULL, 0, NULL);
    filter = 0;
}

Это ошибка в реализации kevent Sierra, или я неправильно понял, как должен работать kevent?

1 ответ

Решение

Проблема здесь в том, что вы не можете "или" вместе EVFILT_READ а также EVFILT_WRITE флаги.

При включении или отключении нескольких фильтров вам нужно позвонить EV_SET() несколько раз, на нескольких evset структур.

Нефункциональный код в приведенном выше примере:

struct kevent evset[2];
struct kevent *ev_p = evset;

if (read) {
    ef->read = read;
    filter |= EVFILT_READ;
}

if (write) {
    ef->write = write;
    filter |= EVFILT_WRITE;
}
ef->error = error;

EV_SET(ev_p++, fd, filter, EV_ADD | EV_ENABLE, 0, 0, ef);
event(el->kq, evset, ev_p - evset, NULL, 0, NULL)

будет выглядеть так:

int count = 0;
struct ev_set[2];

if (read) {
    ef->read = read;
    EV_SET(ev_set[count++], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, ef);
}

if (write) {
    ef->write = write;
    EV_SET(ev_set[count++], fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, ef);
}
ef->error = error;
kevent(el->kq, ev_set, count, NULL, 0, NULL)

После внесения этого изменения все заработало как положено.

Другие вопросы по тегам