Асинхронная интеграция 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)
После внесения этого изменения все заработало как положено.