From 6efeaf4b39deee79a603b5310ddccbd3de83dc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 29 Sep 2018 14:05:56 +0200 Subject: [PATCH 01/75] try to fix issue #3 by special casing listening sockets in is_not_yet_connected_stream_socket() --- src/epoll.c | 33 ++++++++++++++++++------- test/epoll-test.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 0921684..206ee05 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -124,15 +124,30 @@ kqueue_load_state(int kq, uint32_t key, uint16_t *val) static int is_not_yet_connected_stream_socket(int s) { - int type; - socklen_t length = sizeof(int); - - if (getsockopt(s, SOL_SOCKET, SO_TYPE, &type, &length) == 0 && - (type == SOCK_STREAM || type == SOCK_SEQPACKET)) { - struct sockaddr name; - socklen_t namelen = 0; - if (getpeername(s, &name, &namelen) < 0 && errno == ENOTCONN) { - return 1; + + { + int val; + socklen_t length = sizeof(int); + + if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, /**/ + &val, &length) == 0 && + val) { + return 0; + } + } + + { + int type; + socklen_t length = sizeof(int); + + if (getsockopt(s, SOL_SOCKET, SO_TYPE, &type, &length) == 0 && + (type == SOCK_STREAM || type == SOCK_SEQPACKET)) { + struct sockaddr name; + socklen_t namelen = 0; + if (getpeername(s, &name, &namelen) < 0 && + errno == ENOTCONN) { + return 1; + } } } diff --git a/test/epoll-test.c b/test/epoll-test.c index 820df54..e7b3b11 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -928,6 +928,67 @@ test17() return 0; } +static int +test17_with_listen() +{ + int sock = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sock < 0) { + return -1; + } + + int enable = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, /**/ + &enable, sizeof(int)) < 0) { + return (-1); + } + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(1337); + if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) { + return (-1); + } + + if (bind(sock, (struct sockaddr const *)&addr, sizeof(addr)) < 0) { + err(1, "bind"); + return (-1); + } + + if (listen(sock, 5) < 0) { + return (-1); + } + + int ep = epoll_create1(EPOLL_CLOEXEC); + if (ep < 0) { + return -1; + } + + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = sock; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, sock, &event) < 0) { + return -1; + } + + int ret; + + for (int i = 0; i < 3; ++i) { + ret = epoll_wait(ep, &event, 1, 100); + if (ret != 0) { + fprintf(stderr, "ret not 0\n"); + return -1; + } + + usleep(100000); + } + + close(ep); + close(sock); + + return 0; +} + static int test18() { @@ -1383,6 +1444,7 @@ main() TEST(test16(true)); TEST(test16(false)); TEST(test17()); + TEST(test17_with_listen()); TEST(test18()); TEST(test20(fd_tcp_socket)); TEST(test20(fd_domain_socket)); From 7ebe14add060128abf318daf7fba9970ecafe079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 7 Oct 2018 23:59:22 +0200 Subject: [PATCH 02/75] ignore EVFILT_WRITE deregistration ENOENT errors (issue #4) Submitted by sghctoma. --- src/epoll.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 206ee05..771a7f8 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -288,9 +288,15 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) return 0; } - /* ignore EVFILT_WRITE registration EINVAL errors (some fd - * types such as kqueues themselves don't support it) */ - if (i == 1 && kev[i].data == EINVAL) { + /* + * Ignore EVFILT_WRITE registration EINVAL errors (some fd + * types such as kqueues themselves don't support it). + * Also ignore ENOENT -- this happens when trying to remove a + * previously added fd where the EVFILT_WRITE registration + * failed. + */ + if (i == 1 && + (kev[i].data == EINVAL || kev[i].data == ENOENT)) { continue; } From 2b2d977ed2e73d295f74011fb1c93a5a76ebe67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 8 Oct 2018 00:00:14 +0200 Subject: [PATCH 03/75] add test for correct EVFILT_WRITE deregistration (issue #4) --- test/epoll-test.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index e7b3b11..7746137 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1418,6 +1418,39 @@ test24(int (*fd_fun)(int fds[3])) return 0; } +static int +test_recursive_register() +{ + /* TODO: Check that this test works the same under Linux. */ + + int ep = epoll_create1(EPOLL_CLOEXEC); + if (ep < 0) { + return -1; + } + + int ep_inner = epoll_create1(EPOLL_CLOEXEC); + if (ep_inner < 0) { + return -1; + } + + struct epoll_event event; + event.events = EPOLLOUT; + event.data.fd = ep_inner; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, ep_inner, &event) < 0) { + return -1; + } + + if (epoll_ctl(ep, EPOLL_CTL_DEL, ep_inner, NULL) < 0) { + return -1; + } + + close(ep_inner); + close(ep); + + return 0; +} + int main() { @@ -1452,6 +1485,7 @@ main() // TEST(test22()); TEST(test23()); TEST(test24(fd_tcp_socket)); + TEST(test_recursive_register()); TEST(testxx()); return 0; From f1ced859984ad61453e0cc25f12a3fae5b1a48bd Mon Sep 17 00:00:00 2001 From: sghctoma Date: Sat, 24 Nov 2018 14:30:40 +0100 Subject: [PATCH 04/75] Add tests for closed file descriptors (cherry picked from commit 083ef491cfd5a774d7d3fda39876edaa035ef668) --- test/epoll-test.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index 7746137..5c96118 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1451,6 +1451,93 @@ test_recursive_register() return 0; } +static int +test_remove_closed() +{ + int ep = epoll_create1(EPOLL_CLOEXEC); + if (ep < 0) { + return -1; + } + + int fds[3]; + if (fd_pipe(fds) < 0) { + return -1; + } + + struct epoll_event event = {0}; + event.events = EPOLLIN; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, fds[0], &event) < 0) { + return -1; + } + + close(fds[0]); + close(fds[1]); + + // Trying to delete an event that was already deleted by closing the + // associated fd should fail. + if (epoll_ctl(ep, EPOLL_CTL_DEL, fds[0], &event) != -1) { + return -1; + } + + close(ep); + return 0; +} + +static int +test_same_fd_value() +{ + int ep = epoll_create1(EPOLL_CLOEXEC); + if (ep < 0) { + return -1; + } + + int fds[3]; + if (fd_pipe(fds) < 0) { + return -1; + } + + struct epoll_event event = {0}; + event.events = EPOLLIN; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, fds[0], &event) < 0) { + return -1; + } + + int ret; + close(fds[0]); + close(fds[1]); + + // Creating new pipe. The file descriptors will have the same numerical + // values as the previous ones. + if (fd_pipe(fds) < 0) { + return -1; + } + + // If status of closed fds would not be cleared, adding an event with the fd + // that has the same numerical value as the closed one would fail. + struct epoll_event event2 = {0}; + event2.events = EPOLLIN; + if ((ret = epoll_ctl(ep, EPOLL_CTL_ADD, fds[0], &event2)) < 0) { + return -1; + } + + pthread_t writer_thread; + pthread_create(&writer_thread, NULL, sleep_then_write, + (void *)(intptr_t)(fds[1])); + + if ((ret = epoll_wait(ep, &event, 1, 300)) != 1) { + return -1; + } + + pthread_join(writer_thread, NULL); + + close(ep); + close(fds[0]); + close(fds[1]); + return 0; +} + int main() { @@ -1486,6 +1573,8 @@ main() TEST(test23()); TEST(test24(fd_tcp_socket)); TEST(test_recursive_register()); + TEST(test_remove_closed()); + TEST(test_same_fd_value()); TEST(testxx()); return 0; From e0a1d7539971052393c3b84053690d10fd2cd426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 4 Jun 2019 20:54:22 +0200 Subject: [PATCH 05/75] add parens around return values --- src/epoll.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 771a7f8..31bd75d 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -216,7 +216,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) poll_fd = -1; poll_epoll_fd = -1; poll_ptr = NULL; - return 0; + return (0); } if (!(flags & KQUEUE_STATE_REGISTERED)) { @@ -267,25 +267,25 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) int ret = kevent(fd, kev, 2, kev, 2, NULL); if (ret < 0) { - return -1; + return (-1); } if (ret != 2) { errno = EINVAL; - return -1; + return (-1); } for (int i = 0; i < 2; ++i) { if (!(kev[i].flags & EV_ERROR)) { errno = EINVAL; - return -1; + return (-1); } if (kev[i].data == ENODEV && poll_fd < 0) { poll_fd = fd2; poll_epoll_fd = fd; poll_ptr = ev->data.ptr; - return 0; + return (0); } /* @@ -302,7 +302,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) if (kev[i].data != 0) { errno = kev[i].data; - return -1; + return (-1); } } @@ -310,7 +310,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) EV_SET(&kev[0], fd2, EVFILT_READ, EV_ENABLE | EV_FORCEONESHOT, 0, 0, ev->data.ptr); if (kevent(fd, kev, 1, NULL, 0, NULL) < 0) { - return -1; + return (-1); } flags |= KQUEUE_STATE_NYCSS; @@ -318,7 +318,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) struct stat statbuf; if (fstat(fd2, &statbuf) < 0) { - return -1; + return (-1); } if (S_ISFIFO(statbuf.st_mode)) { @@ -332,7 +332,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) return (-1); } - return 0; + return (0); } #undef VAL_BITS From f3ce2bd3f6485f3cb25347a4a720ca4f7af29a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 4 Jun 2019 20:56:22 +0200 Subject: [PATCH 06/75] clear internal state if trying to remove an already closed fd from the epoll instance --- src/epoll.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/epoll.c b/src/epoll.c index 31bd75d..cf13e20 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -301,6 +301,10 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) } if (kev[i].data != 0) { + if (i == 0 && + (kev[i].data == ENOENT || kev[i].data == EBADF)) { + kqueue_save_state(fd, fd2, 0); + } errno = kev[i].data; return (-1); } From 7fca585cbdc93d106ee06c0b3e1fd25ace969184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 4 Jun 2019 20:56:52 +0200 Subject: [PATCH 07/75] don't run interactive test by default --- test/epoll-test.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index 5c96118..6727515 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -21,6 +21,9 @@ #include #include +/* Uncomment this if you want the interactive test. */ +/* #define INTERACTIVE_TESTS */ + #define XSTR(a) STR(a) #define STR(a) #a @@ -1570,7 +1573,9 @@ main() TEST(test20(fd_domain_socket)); TEST(test21()); // TEST(test22()); +#ifdef INTERACTIVE_TESTS TEST(test23()); +#endif TEST(test24(fd_tcp_socket)); TEST(test_recursive_register()); TEST(test_remove_closed()); From b026aad7396a25b5b9f67f84b5a4aa137c4cac2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 4 Jun 2019 20:58:11 +0200 Subject: [PATCH 08/75] adjust test_same_fd_value test so that epoll_ctl EPOLL_CTL_DEL is called --- test/epoll-test.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index 6727515..1af3b32 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1511,6 +1511,15 @@ test_same_fd_value() close(fds[0]); close(fds[1]); + // Note: This wouldn't be needed under Linux as the close() calls above + // properly removes the descriptor from the epoll instance. However, in + // our epoll emulation we cannot (yet?) reliably detect if a descriptor + // has been closed before it is deleted from the epoll instance. + // See also: https://github.com/jiixyj/epoll-shim/pull/7 + if (epoll_ctl(ep, EPOLL_CTL_DEL, fds[0], &event) != -1) { + return -1; + } + // Creating new pipe. The file descriptors will have the same numerical // values as the previous ones. if (fd_pipe(fds) < 0) { From fba999a74f11db16a3ec3cf956bfe0fdf83a5933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 4 Jun 2019 21:23:52 +0200 Subject: [PATCH 09/75] add README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4281270 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +epoll-shim +========== + +This is a small library that implements epoll on top of kqueue. +It has been successfully used to port libinput, libevdev, Wayland and more +software to FreeBSD: https://www.freshports.org/devel/libepoll-shim/ + +It may be useful for porting other software that uses epoll as well. + +There are some tests inside `test/`. They should also compile under Linux and +can be used to verify proper epoll behavior. + +However, this library contains some very ugly hacks and workarounds. For +example: + - When using timerfd or signalfd, `read` and `close` are redefined as macros + to internal helper functions. This is needed as there is some internal + context that has to be free'd properly. This means that you shouldn't create + a timerfd/signalfd in one part of a program and close it in a different part + where `sys/timerfd.h` isn't included. The context would leak. Luckily, + software such as `libinput` behaves very nicely and puts all timerfd related + code in a single source file. + - There is exactly one static int reserved for fds that can be polled but are + not supported by kqueue under FreeBSD. This includes graphics or sound + devices under `/dev`. You can only have one of them throughout all epoll + instances in your process! From 0cf1332d37b59c57ca93c499c3268ce22dcc651b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 21 Jun 2019 21:07:17 +0200 Subject: [PATCH 10/75] enable running tests with CTest --- CMakeLists.txt | 7 ++++++- test/CMakeLists.txt | 3 +++ test/epoll-test.c | 44 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91590d3..c1150d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,13 @@ cmake_minimum_required(VERSION 3.9) project(epoll-shim C) +include(CTest) + set(CMAKE_C_STANDARD 99) set(CMAKE_C_EXTENSIONS ON) add_subdirectory(src) -add_subdirectory(test) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dbac513..8f32559 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,12 @@ add_executable(epoll-test epoll-test.c) target_link_libraries(epoll-test PRIVATE epoll-shim) +add_test(NAME epoll-test COMMAND epoll-test) add_executable(expire-five expire-five.c) target_link_libraries(expire-five PRIVATE epoll-shim) +add_test(NAME expire-five COMMAND expire-five) add_executable(kqueue-state kqueue-state.c) target_include_directories(kqueue-state PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../include") +add_test(NAME kqueue-state COMMAND kqueue-state) diff --git a/test/epoll-test.c b/test/epoll-test.c index 1af3b32..b40eee1 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -29,9 +29,14 @@ #define TEST(fun) \ if ((fun) != 0) { \ - printf(STR((fun)) " failed\n"); \ + fprintf(stderr, STR((fun)) " failed\n"); \ + r = 1; \ } else { \ - printf(STR((fun)) " successful\n"); \ + fprintf(stderr, STR((fun)) " successful\n"); \ + if (check_for_fd_leaks() != 0) { \ + fprintf(stderr, "but there was a fd leak...\n"); \ + r = 1; \ + }; \ } static int @@ -762,9 +767,12 @@ test15() return 0; } +static int fd_leak_test_a; +static int fd_leak_test_b; static int -testxx() +check_for_fd_leaks() { + int r = 0; /* test that all fds of previous tests have been closed successfully */ int fds[3]; @@ -772,13 +780,14 @@ testxx() return -1; } - if (fds[0] != 3 || fds[1] != 4) { - return -1; + if (fds[0] != fd_leak_test_a || fds[1] != fd_leak_test_b) { + r = -1; } close(fds[0]); close(fds[1]); - return 0; + + return r; } static void * @@ -1526,8 +1535,9 @@ test_same_fd_value() return -1; } - // If status of closed fds would not be cleared, adding an event with the fd - // that has the same numerical value as the closed one would fail. + // If status of closed fds would not be cleared, adding an event with + // the fd that has the same numerical value as the closed one would + // fail. struct epoll_event event2 = {0}; event2.events = EPOLLIN; if ((ret = epoll_ctl(ep, EPOLL_CTL_ADD, fds[0], &event2)) < 0) { @@ -1553,6 +1563,21 @@ test_same_fd_value() int main() { + int r = 0; + + /* We check for fd leaks after each test. Remember fd numbers for + * checking here. */ + { + int fds[3]; + if (fd_pipe(fds) < 0) { + return 1; + } + fd_leak_test_a = fds[0]; + fd_leak_test_b = fds[1]; + close(fds[0]); + close(fds[1]); + } + TEST(test1()); TEST(test2()); TEST(test3()); @@ -1590,6 +1615,5 @@ main() TEST(test_remove_closed()); TEST(test_same_fd_value()); - TEST(testxx()); - return 0; + return r; } From db0f2de07bf101e5216a5cc06187985a0ba8a495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 09:30:28 +0200 Subject: [PATCH 11/75] use kqueue EVFILT_TIMER based implementation for timerfd if possible --- src/timerfd.c | 285 ++++++++++++++++++++++++++++++++++---------- test/CMakeLists.txt | 4 + 2 files changed, 223 insertions(+), 66 deletions(-) diff --git a/src/timerfd.c b/src/timerfd.c index b42a3ab..77e93fc 100644 --- a/src/timerfd.c +++ b/src/timerfd.c @@ -3,10 +3,13 @@ #undef close #include +#include +#include #include #include +#include #include #include #include @@ -14,11 +17,26 @@ #include #include +enum timerfd_kind { + TIMERFD_KIND_UNDETERMINED, + TIMERFD_KIND_SIMPLE, + TIMERFD_KIND_COMPLEX, +}; + struct timerfd_context { int fd; - pthread_t worker; - timer_t timer; int flags; + enum timerfd_kind kind; + union { + struct { + struct itimerspec current_itimerspec; + } simple; + struct { + pthread_t worker; + timer_t timer; + uint64_t current_expirations; + } complex; + }; struct timerfd_context *next; }; @@ -37,13 +55,17 @@ get_timerfd_context(int fd, bool create_new) if (create_new) { struct timerfd_context *new_ctx = - calloc(1, sizeof(struct timerfd_context)); + malloc(sizeof(struct timerfd_context)); if (!new_ctx) { return NULL; } - new_ctx->fd = -1; - new_ctx->next = timerfd_contexts; + + *new_ctx = (struct timerfd_context){ + .fd = -1, + .next = timerfd_contexts, + }; timerfd_contexts = new_ctx; + return new_ctx; } @@ -55,6 +77,8 @@ worker_function(void *arg) { struct timerfd_context *ctx = arg; + uint64_t total_expirations = 0; + siginfo_t info; sigset_t rt_set; sigset_t block_set; @@ -76,59 +100,48 @@ worker_function(void *arg) if (sigwaitinfo(&rt_set, &info) != SIGRTMIN) { break; } + total_expirations += 1 + timer_getoverrun(ctx->complex.timer); EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, - (void *)(intptr_t)timer_getoverrun(ctx->timer)); + (void *)(uintptr_t)total_expirations); (void)kevent(ctx->fd, &kev, 1, NULL, 0, NULL); } return NULL; } -static int -timerfd_create_impl(int clockid, int flags) +static errno_t +upgrade_to_complex_timer(struct timerfd_context *ctx, int clockid) { - if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) { - return EINVAL; - } + errno_t err; - if (flags & ~(TFD_CLOEXEC | TFD_NONBLOCK)) { - return EINVAL; + if (ctx->kind == TIMERFD_KIND_COMPLEX) { + return 0; } - struct timerfd_context *ctx = get_timerfd_context(-1, true); - if (!ctx) { - errno = ENOMEM; - return -1; - } + if (ctx->kind == TIMERFD_KIND_SIMPLE) { + struct kevent kev[1]; + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); + (void)kevent(ctx->fd, kev, nitems(kev), NULL, 0, NULL); - ctx->fd = kqueue(); - if (ctx->fd < 0) { - return -1; + ctx->kind = TIMERFD_KIND_UNDETERMINED; } - ctx->flags = flags; + assert(ctx->kind == TIMERFD_KIND_UNDETERMINED); struct kevent kev; EV_SET(&kev, 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); if (kevent(ctx->fd, &kev, 1, NULL, 0, NULL) < 0) { - close(ctx->fd); - ctx->fd = -1; - return -1; + assert(errno != 0); + return errno; } - if (pthread_create(&ctx->worker, NULL, worker_function, ctx) < 0) { - close(ctx->fd); - ctx->fd = -1; - return -1; + if ((err = pthread_create(&ctx->complex.worker, /**/ + NULL, worker_function, ctx)) != 0) { + return err; } - int ret = kevent(ctx->fd, NULL, 0, &kev, 1, NULL); - if (ret < 0) { - pthread_kill(ctx->worker, SIGRTMIN + 1); - pthread_join(ctx->worker, NULL); - close(ctx->fd); - ctx->fd = -1; - return -1; + if (kevent(ctx->fd, NULL, 0, &kev, 1, NULL) < 0) { + goto f0; } int tid = (int)(intptr_t)kev.udata; @@ -137,53 +150,184 @@ timerfd_create_impl(int clockid, int flags) .sigev_signo = SIGRTMIN, .sigev_notify_thread_id = tid}; - if (timer_create(clockid, &sigev, &ctx->timer) < 0) { - pthread_kill(ctx->worker, SIGRTMIN + 1); - pthread_join(ctx->worker, NULL); - close(ctx->fd); - ctx->fd = -1; - return -1; + if (timer_create(clockid, &sigev, &ctx->complex.timer) < 0) { + goto f0; } - return ctx->fd; + ctx->complex.current_expirations = 0; + ctx->kind = TIMERFD_KIND_COMPLEX; + return 0; + +f0: + assert(errno != 0); + err = errno; + pthread_kill(ctx->complex.worker, SIGRTMIN + 1); + pthread_join(ctx->complex.worker, NULL); + return err; +} + +static errno_t +timerfd_create_impl(int clockid, int flags, int *fd) +{ + errno_t err; + + if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) { + return EINVAL; + } + + if (flags & ~(TFD_CLOEXEC | TFD_NONBLOCK)) { + return EINVAL; + } + + struct timerfd_context *ctx = get_timerfd_context(-1, true); + if (!ctx) { + return ENOMEM; + } + + ctx->fd = kqueue(); + if (ctx->fd < 0) { + assert(errno != 0); + return errno; + } + + ctx->flags = flags; + ctx->kind = TIMERFD_KIND_UNDETERMINED; + + if (clockid == CLOCK_REALTIME) { + if ((err = upgrade_to_complex_timer(ctx, /**/ + CLOCK_REALTIME)) != 0) { + goto f0; + } + } + + *fd = ctx->fd; + return 0; + +f0: + close(ctx->fd); + ctx->fd = -1; + return err; } int timerfd_create(int clockid, int flags) { + int fd; + errno_t err; + pthread_mutex_lock(&timerfd_context_mtx); - int ret = timerfd_create_impl(clockid, flags); + err = timerfd_create_impl(clockid, flags, &fd); pthread_mutex_unlock(&timerfd_context_mtx); - return ret; + + if (err != 0) { + errno = err; + return -1; + } + + return fd; } -static int -timerfd_settime_impl( - int fd, int flags, const struct itimerspec *new, struct itimerspec *old) +static errno_t +timerfd_settime_impl(int fd, int flags, const struct itimerspec *new, + struct itimerspec *old) { - struct timerfd_context *ctx = get_timerfd_context(fd, false); + errno_t err; + struct timerfd_context *ctx; + + if (!new) { + return EFAULT; + } + + ctx = get_timerfd_context(fd, false); if (!ctx) { - errno = EINVAL; - return -1; + return EINVAL; } if (flags & ~(TFD_TIMER_ABSTIME)) { - errno = EINVAL; - return -1; + return EINVAL; + } + + if ((flags & TFD_TIMER_ABSTIME) || + ((new->it_interval.tv_sec != 0 || new->it_interval.tv_nsec != 0) && + (new->it_interval.tv_sec != new->it_value.tv_sec || + new->it_interval.tv_nsec != new->it_value.tv_nsec))) { + if ((err = upgrade_to_complex_timer(ctx, /**/ + CLOCK_MONOTONIC)) != 0) { + return err; + } } - return timer_settime(ctx->timer, - (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, new, old); + if (ctx->kind == TIMERFD_KIND_COMPLEX) { + if (timer_settime(ctx->complex.timer, + (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, /**/ + new, old) < 0) { + return errno; + } + } else { + struct kevent kev[1]; + int oneshot_flag; + int64_t micros; + + if (old) { + *old = ctx->simple.current_itimerspec; + } + + if (new->it_value.tv_sec == 0 && new->it_value.tv_nsec == 0) { + struct kevent kev[1]; + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); + (void)kevent(ctx->fd, kev, nitems(kev), NULL, 0, NULL); + } else { + if (__builtin_mul_overflow(new->it_value.tv_sec, + 1000000, µs) || + __builtin_add_overflow(micros, + new->it_value.tv_nsec / 1000, µs)) { + return EOVERFLOW; + } + + if ((new->it_value.tv_nsec % 1000) && + __builtin_add_overflow(micros, 1, µs)) { + return EOVERFLOW; + } + + if (new->it_interval.tv_sec == 0 && + new->it_interval.tv_nsec == 0) { + oneshot_flag = EV_ONESHOT; + } else { + oneshot_flag = 0; + } + + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_ADD | oneshot_flag, + NOTE_USECONDS, micros, 0); + + if (kevent(ctx->fd, kev, nitems(kev), /**/ + NULL, 0, NULL) < 0) { + return errno; + } + } + + ctx->simple.current_itimerspec = *new; + ctx->kind = TIMERFD_KIND_SIMPLE; + } + + return 0; } int -timerfd_settime( - int fd, int flags, const struct itimerspec *new, struct itimerspec *old) +timerfd_settime(int fd, int flags, const struct itimerspec *new, + struct itimerspec *old) { + errno_t err; + pthread_mutex_lock(&timerfd_context_mtx); - int ret = timerfd_settime_impl(fd, flags, new, old); + err = timerfd_settime_impl(fd, flags, new, old); pthread_mutex_unlock(&timerfd_context_mtx); - return ret; + + if (err != 0) { + errno = err; + return -1; + } + + return 0; } #if 0 @@ -207,9 +351,9 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) struct timespec timeout = {0, 0}; struct kevent kev; - int ret = kevent( - fd, NULL, 0, &kev, 1, (flags & TFD_NONBLOCK) ? &timeout : NULL); - if (ret == -1) { + int ret = kevent(fd, NULL, 0, &kev, 1, + (flags & TFD_NONBLOCK) ? &timeout : NULL); + if (ret < 0) { return -1; } @@ -218,7 +362,14 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) return -1; } - uint64_t nr_expired = 1 + (uint64_t)kev.udata; + uint64_t nr_expired; + if (ctx->kind == TIMERFD_KIND_COMPLEX) { + uint64_t expired_new = (uint64_t)kev.udata; + nr_expired = expired_new - ctx->complex.current_expirations; + ctx->complex.current_expirations = expired_new; + } else { + nr_expired = kev.data; + } memcpy(buf, &nr_expired, sizeof(uint64_t)); return sizeof(uint64_t); @@ -227,9 +378,11 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) int timerfd_close(struct timerfd_context *ctx) { - timer_delete(ctx->timer); - pthread_kill(ctx->worker, SIGRTMIN + 1); - pthread_join(ctx->worker, NULL); + if (ctx->kind == TIMERFD_KIND_COMPLEX) { + timer_delete(ctx->complex.timer); + pthread_kill(ctx->complex.worker, SIGRTMIN + 1); + pthread_join(ctx->complex.worker, NULL); + } int ret = close(ctx->fd); ctx->fd = -1; return ret; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8f32559..d851e4e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,3 +10,7 @@ add_executable(kqueue-state kqueue-state.c) target_include_directories(kqueue-state PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../include") add_test(NAME kqueue-state COMMAND kqueue-state) + +add_executable(many-timers many-timers.c) +target_link_libraries(many-timers PRIVATE epoll-shim) +add_test(NAME many-timers COMMAND many-timers) From b3be554abd71db3d5ed44ed8d780b9ced4c65c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 10:22:15 +0200 Subject: [PATCH 12/75] add missing test source file --- test/many-timers.c | 229 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 test/many-timers.c diff --git a/test/many-timers.c b/test/many-timers.c new file mode 100644 index 0000000..0c19999 --- /dev/null +++ b/test/many-timers.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define REQUIRE(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "REQUIRE in line %d failed.\n", \ + __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static bool +is_fast_timer(int fd) +{ + struct kevent kev[1]; + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); + + bool is_fast = kevent(fd, kev, nitems(kev), NULL, 0, NULL) == 0; + close(fd); + return (is_fast); +} + +int +main() +{ + int timer_fds[1024]; + int i; + + for (i = 0; i < nitems(timer_fds); ++i) { + REQUIRE((timer_fds[i] = timerfd_create(CLOCK_MONOTONIC, /**/ + TFD_CLOEXEC | TFD_NONBLOCK)) >= 0); + } + + struct pollfd pfd; + struct timespec b, e; + struct itimerspec time; + int timerfd; + + { + timerfd = timer_fds[0]; + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 100000000, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + pfd = (struct pollfd){.fd = timerfd, .events = POLLIN}; + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &b) == 0); + REQUIRE(poll(&pfd, 1, -1) == 1); + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); + timespecsub(&e, &b, &e); + REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 100000000 && + e.tv_nsec < 150000000); + + REQUIRE(is_fast_timer(timerfd)); + } + + { + timerfd = timer_fds[1]; + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 100000000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 100000000, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + pfd = (struct pollfd){.fd = timerfd, .events = POLLIN}; + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &b) == 0); + REQUIRE(poll(&pfd, 1, -1) == 1); + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); + timespecsub(&e, &b, &e); + REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 100000000 && + e.tv_nsec < 150000000); + + poll(&pfd, 1, -1); + uint64_t timeouts; + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 1); + + usleep(230000); + + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 2); + + REQUIRE(is_fast_timer(timerfd)); + } + + { + timerfd = timer_fds[2]; + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 100000000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 100000001, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + pfd = (struct pollfd){.fd = timerfd, .events = POLLIN}; + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &b) == 0); + REQUIRE(poll(&pfd, 1, -1) == 1); + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); + timespecsub(&e, &b, &e); + REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 100000000 && + e.tv_nsec < 150000000); + + poll(&pfd, 1, -1); + uint64_t timeouts; + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 1); + + usleep(230000); + + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 2); + + REQUIRE(!is_fast_timer(timerfd)); + } + + { + timerfd = timer_fds[3]; + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 100000000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 100000000, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &b) == 0); + + pfd = (struct pollfd){.fd = timerfd, .events = POLLIN}; + REQUIRE(poll(&pfd, 1, -1) == 1); + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 50000000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 50000000, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + REQUIRE(poll(&pfd, 1, -1) == 1); + + uint64_t timeouts; + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 1); + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); + timespecsub(&e, &b, &e); + REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 150000000 && + e.tv_nsec < 200000000); + + REQUIRE(is_fast_timer(timerfd)); + } + + { + timerfd = timer_fds[4]; + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 100000000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 100000000, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &b) == 0); + + pfd = (struct pollfd){.fd = timerfd, .events = POLLIN}; + REQUIRE(poll(&pfd, 1, -1) == 1); + + uint64_t timeouts; + REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) == + (ssize_t)sizeof(timeouts)); + REQUIRE(timeouts == 1); + + time = (struct itimerspec){ + .it_value.tv_sec = 0, + .it_value.tv_nsec = 0, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 0, + }; + + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + REQUIRE(poll(&pfd, 1, 200) == 0); + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); + timespecsub(&e, &b, &e); + REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 300000000 && + e.tv_nsec < 350000000); + + time = (struct itimerspec){ + .it_value.tv_sec = 1, + .it_value.tv_nsec = 0, + .it_interval.tv_sec = 1, + .it_interval.tv_nsec = 0, + }; + REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); + + REQUIRE(is_fast_timer(timerfd)); + } +} From 4eff41b18f3332cf1b2714544a862522d95fa3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 10:26:49 +0200 Subject: [PATCH 13/75] refactor build system slightly, install pkgconfig file --- CMakeLists.txt | 32 ++++++++++++++++++++++++++++---- libepoll-shim.pc.cmakein | 12 ++++++++++++ src/CMakeLists.txt | 20 ++++++++------------ 3 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 libepoll-shim.pc.cmakein diff --git a/CMakeLists.txt b/CMakeLists.txt index c1150d1..15e443e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ -cmake_minimum_required(VERSION 3.9) -project(epoll-shim C) +cmake_minimum_required(VERSION 3.14) +project(epoll-shim LANGUAGES C) + +option(BUILD_SHARED_LIBS "build libepoll-shim as shared lib" ON) include(CTest) @@ -8,6 +10,28 @@ set(CMAKE_C_EXTENSIONS ON) add_subdirectory(src) -if(BUILD_TESTING) - add_subdirectory(test) +if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + if(BUILD_TESTING) + add_subdirectory(test) + endif() + + include(GNUInstallDirs) + + set(CMAKE_INSTALL_PKGCONFIGDIR + "libdata/pkgconfig" + CACHE PATH "Installation directory for pkgconfig (.pc) files") + mark_as_advanced(CMAKE_INSTALL_PKGCONFIGDIR) + + install(TARGETS epoll-shim LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + install(FILES "${PROJECT_SOURCE_DIR}/include/sys/epoll.h" # + "${PROJECT_SOURCE_DIR}/include/sys/signalfd.h" + "${PROJECT_SOURCE_DIR}/include/sys/timerfd.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim/sys") + + if(BUILD_SHARED_LIBS) + configure_file("${PROJECT_SOURCE_DIR}/libepoll-shim.pc.cmakein" + "${PROJECT_BINARY_DIR}/libepoll-shim.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/libepoll-shim.pc" + DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") + endif() endif() diff --git a/libepoll-shim.pc.cmakein b/libepoll-shim.pc.cmakein new file mode 100644 index 0000000..62388db --- /dev/null +++ b/libepoll-shim.pc.cmakein @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: epoll-shim +URL: https://github.com/jiixyj/epoll-shim +Description: Small epoll implementation using kqueue +Version: +Libs: -L${libdir} -lepoll-shim +Libs.private: -pthread -lrt +Cflags: -I${includedir}/libepoll-shim diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e35eb0..f7c3a1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,18 +1,14 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -set(INCLUDES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../include") - -add_library(epoll-shim SHARED epoll.c timerfd.c signalfd.c common.c) +add_library(epoll-shim + epoll.c + timerfd.c + signalfd.c + common.c) target_link_libraries(epoll-shim PUBLIC Threads::Threads rt) -target_include_directories(epoll-shim PUBLIC "${INCLUDES_DIR}") +target_include_directories(epoll-shim PUBLIC "${PROJECT_SOURCE_DIR}/include") -set_target_properties(epoll-shim PROPERTIES - LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../Version.map") +target_link_options(epoll-shim PRIVATE + "LINKER:--version-script=${PROJECT_SOURCE_DIR}/Version.map") set_target_properties(epoll-shim PROPERTIES SOVERSION 0) - - -install(TARGETS epoll-shim LIBRARY DESTINATION lib) -install(FILES "${INCLUDES_DIR}/sys/epoll.h" DESTINATION include/libepoll-shim/sys) -install(FILES "${INCLUDES_DIR}/sys/signalfd.h" DESTINATION include/libepoll-shim/sys) -install(FILES "${INCLUDES_DIR}/sys/timerfd.h" DESTINATION include/libepoll-shim/sys) From 8d66fa5c284a41de672a95aa7090785764157974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 10:34:25 +0200 Subject: [PATCH 14/75] make epoll-shim link privately against Threads::Threads --- src/CMakeLists.txt | 2 +- test/CMakeLists.txt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7c3a1d..f3e06d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(epoll-shim timerfd.c signalfd.c common.c) -target_link_libraries(epoll-shim PUBLIC Threads::Threads rt) +target_link_libraries(epoll-shim PRIVATE Threads::Threads rt) target_include_directories(epoll-shim PUBLIC "${PROJECT_SOURCE_DIR}/include") target_link_options(epoll-shim PRIVATE diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d851e4e..c201de8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,8 @@ +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + add_executable(epoll-test epoll-test.c) -target_link_libraries(epoll-test PRIVATE epoll-shim) +target_link_libraries(epoll-test PRIVATE epoll-shim Threads::Threads) add_test(NAME epoll-test COMMAND epoll-test) add_executable(expire-five expire-five.c) From 58aa9a55aac43149a370179753cdf1d1716475ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 10:38:43 +0200 Subject: [PATCH 15/75] remove old Makefile --- Makefile | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index df3f17f..0000000 --- a/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -LIB= epoll-shim -SHLIB_MAJOR= 0 -SRCS= src/epoll.c src/timerfd.c src/signalfd.c src/common.c -INCS= include/sys/epoll.h include/sys/timerfd.h include/sys/signalfd.h -VERSION_MAP= Version.map - -LIBDIR= /usr/local/lib -INCSDIR= /usr/local/include/libepoll-shim/sys - -CFLAGS+= -I${.CURDIR}/include -pthread -Wall -Wextra -Wno-missing-prototypes -Wno-padded -Wno-missing-variable-declarations -Wno-thread-safety-analysis -LDFLAGS+= -pthread -lrt - -distrib-dirs: - mkdir -p "${DESTDIR}/${LIBDIR}" - mkdir -p "${DESTDIR}/${INCSDIR}" - -.include From 7f13ee455f0a390876de985bc2c939bb65c9c5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 10:45:50 +0200 Subject: [PATCH 16/75] add installation instructions --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 4281270..b04d582 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,22 @@ example: not supported by kqueue under FreeBSD. This includes graphics or sound devices under `/dev`. You can only have one of them throughout all epoll instances in your process! + + +Installation +------------ + +Run the following commands to build libepoll-shim: + + $ mkdir build + $ cd build + $ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo + $ cmake --build . + +To run the tests: + + $ ctest --output-on-failure + +To install (as root): + + # cmake --build . --target install From baacd407c9ff3fe035350bbedf4becf7e0409f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 13:00:26 +0200 Subject: [PATCH 17/75] fix sign of errno value --- src/epoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epoll.c b/src/epoll.c index cf13e20..5a05a54 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -332,7 +332,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) } if ((e = kqueue_save_state(fd, fd2, flags)) < 0) { - errno = e; + errno = -e; return (-1); } From cb7519731e2f0bc0068f8c43b726ce936d8a9458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 13:07:48 +0200 Subject: [PATCH 18/75] fix sign of errno value --- src/epoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epoll.c b/src/epoll.c index 5a05a54..3d61515 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -179,7 +179,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) } if ((e = kqueue_load_state(fd, fd2, &flags)) < 0) { - errno = e; + errno = -e; return (-1); } From 1605b570d28d9ce96b878e0aeab9f1a2edf3eb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 13:12:04 +0200 Subject: [PATCH 19/75] only ask for fd type when adding to epoll fd (avoids fstat'ing invalid fds) --- src/epoll.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 3d61515..b4b47c7 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -320,15 +320,21 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) flags |= KQUEUE_STATE_NYCSS; } - struct stat statbuf; - if (fstat(fd2, &statbuf) < 0) { - return (-1); - } + if (op == EPOLL_CTL_ADD) { + struct stat statbuf; + if (fstat(fd2, &statbuf) < 0) { + /* If the fstat fails for some reason we must clear + * internal state to avoid EEXIST errors in future + * calls to epoll_ctl. */ + (void)kqueue_save_state(fd, fd2, 0); + return (-1); + } - if (S_ISFIFO(statbuf.st_mode)) { - flags |= KQUEUE_STATE_ISFIFO; - } else if (S_ISSOCK(statbuf.st_mode)) { - flags |= KQUEUE_STATE_ISSOCK; + if (S_ISFIFO(statbuf.st_mode)) { + flags |= KQUEUE_STATE_ISFIFO; + } else if (S_ISSOCK(statbuf.st_mode)) { + flags |= KQUEUE_STATE_ISSOCK; + } } if ((e = kqueue_save_state(fd, fd2, flags)) < 0) { From 602e3311e10012d2c5a8efa254012f369506d699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 13:28:56 +0200 Subject: [PATCH 20/75] try to fix infinite loop in test20 --- test/epoll-test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index b40eee1..24bc7f9 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1132,6 +1132,7 @@ test20(int (*fd_fun)(int fds[3])) // fprintf(stderr, "got %d\n", (int)c); } else if (event_result.events == EPOLLOUT) { + write(event.data.fd, &c, 1); // continue } else if (fd_fun == fd_domain_socket && (event_result.events & (EPOLLOUT | EPOLLHUP)) == From 4037460a2d0ba0b7538fe78077ef18fc73c4431b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 14:44:39 +0200 Subject: [PATCH 21/75] fix name of pkg-config file --- CMakeLists.txt | 6 +++--- libepoll-shim.pc.cmakein => epoll-shim.pc.cmakein | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename libepoll-shim.pc.cmakein => epoll-shim.pc.cmakein (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15e443e..36844b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,9 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim/sys") if(BUILD_SHARED_LIBS) - configure_file("${PROJECT_SOURCE_DIR}/libepoll-shim.pc.cmakein" - "${PROJECT_BINARY_DIR}/libepoll-shim.pc" @ONLY) - install(FILES "${PROJECT_BINARY_DIR}/libepoll-shim.pc" + configure_file("${PROJECT_SOURCE_DIR}/epoll-shim.pc.cmakein" + "${PROJECT_BINARY_DIR}/epoll-shim.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/epoll-shim.pc" DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") endif() endif() diff --git a/libepoll-shim.pc.cmakein b/epoll-shim.pc.cmakein similarity index 100% rename from libepoll-shim.pc.cmakein rename to epoll-shim.pc.cmakein From c680517ecd7cfb3520a14e8fbdb998974aa9cfd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 19:44:28 +0200 Subject: [PATCH 22/75] also install epoll-shim.pc when building the static lib --- CMakeLists.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36844b6..92c10c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,10 +28,8 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) "${PROJECT_SOURCE_DIR}/include/sys/timerfd.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim/sys") - if(BUILD_SHARED_LIBS) - configure_file("${PROJECT_SOURCE_DIR}/epoll-shim.pc.cmakein" - "${PROJECT_BINARY_DIR}/epoll-shim.pc" @ONLY) - install(FILES "${PROJECT_BINARY_DIR}/epoll-shim.pc" - DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") - endif() + configure_file("${PROJECT_SOURCE_DIR}/epoll-shim.pc.cmakein" + "${PROJECT_BINARY_DIR}/epoll-shim.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/epoll-shim.pc" + DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") endif() From a790d8d0998c7ca0dd243ad658dcfe7c4f355121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 22 Jun 2019 23:57:44 +0200 Subject: [PATCH 23/75] install cmake config files for consumers --- CMakeLists.txt | 45 +++++++++++++++++++++++++++++++++++---------- src/CMakeLists.txt | 4 +++- test/CMakeLists.txt | 16 +++++++++++----- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c10c6..6b437e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,12 @@ set(CMAKE_C_EXTENSIONS ON) add_subdirectory(src) if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(_namespace "${PROJECT_NAME}") + if(BUILD_TESTING) + file(WRITE "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + "add_library(${_namespace}::epoll-shim ALIAS epoll-shim)\n") + set(${PROJECT_NAME}_DIR "${PROJECT_BINARY_DIR}") add_subdirectory(test) endif() @@ -21,15 +26,35 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) "libdata/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") mark_as_advanced(CMAKE_INSTALL_PKGCONFIGDIR) - - install(TARGETS epoll-shim LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") - install(FILES "${PROJECT_SOURCE_DIR}/include/sys/epoll.h" # - "${PROJECT_SOURCE_DIR}/include/sys/signalfd.h" - "${PROJECT_SOURCE_DIR}/include/sys/timerfd.h" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim/sys") - - configure_file("${PROJECT_SOURCE_DIR}/epoll-shim.pc.cmakein" - "${PROJECT_BINARY_DIR}/epoll-shim.pc" @ONLY) - install(FILES "${PROJECT_BINARY_DIR}/epoll-shim.pc" + set(CMAKE_INSTALL_CMAKEBASEDIR + "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + CACHE PATH "Installation directory for CMake config (.cmake) files") + mark_as_advanced(CMAKE_INSTALL_CMAKEBASEDIR) + + set(CMAKE_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim") + install(TARGETS epoll-shim + EXPORT ${PROJECT_NAME}-targets + LIBRARY + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" TYPE INCLUDE) + + configure_file("${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.pc.cmakein" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") + + install(EXPORT ${PROJECT_NAME}-targets + NAMESPACE "${_namespace}::" + DESTINATION "${CMAKE_INSTALL_CMAKEBASEDIR}") + if(NOT BUILD_SHARED_LIBS) + set(_deps "include(CMakeFindDependencyMacro)\n" + "set(THREADS_PREFER_PTHREAD_FLAG ON)\n" + "find_dependency(Threads)\n") + endif() + file( + WRITE "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + ${_deps} + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-targets.cmake\")\n") + install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_CMAKEBASEDIR}") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f3e06d2..02b284b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,9 @@ add_library(epoll-shim signalfd.c common.c) target_link_libraries(epoll-shim PRIVATE Threads::Threads rt) -target_include_directories(epoll-shim PUBLIC "${PROJECT_SOURCE_DIR}/include") +target_include_directories( + epoll-shim + PUBLIC $) target_link_options(epoll-shim PRIVATE "LINKER:--version-script=${PROJECT_SOURCE_DIR}/Version.map") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c201de8..4c371be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,19 +1,25 @@ +cmake_minimum_required(VERSION 3.14) +project(epoll-shim-tests LANGUAGES C) + +find_package(epoll-shim REQUIRED) + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) add_executable(epoll-test epoll-test.c) -target_link_libraries(epoll-test PRIVATE epoll-shim Threads::Threads) +target_link_libraries(epoll-test + PRIVATE epoll-shim::epoll-shim Threads::Threads) add_test(NAME epoll-test COMMAND epoll-test) add_executable(expire-five expire-five.c) -target_link_libraries(expire-five PRIVATE epoll-shim) +target_link_libraries(expire-five PRIVATE epoll-shim::epoll-shim) add_test(NAME expire-five COMMAND expire-five) add_executable(kqueue-state kqueue-state.c) -target_include_directories(kqueue-state PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_include_directories(kqueue-state + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../include") add_test(NAME kqueue-state COMMAND kqueue-state) add_executable(many-timers many-timers.c) -target_link_libraries(many-timers PRIVATE epoll-shim) +target_link_libraries(many-timers PRIVATE epoll-shim::epoll-shim) add_test(NAME many-timers COMMAND many-timers) From 872ce7e04267049473f7b55dbd2d54b31920a1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 3 Sep 2019 20:46:35 +0200 Subject: [PATCH 24/75] remove superfluous 'libepoll-shim' in pkg-config file (issue #9) --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b437e8..8b632ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,11 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) CACHE PATH "Installation directory for CMake config (.cmake) files") mark_as_advanced(CMAKE_INSTALL_CMAKEBASEDIR) + configure_file("${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.pc.cmakein" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" + DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") + set(CMAKE_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/libepoll-shim") install(TARGETS epoll-shim EXPORT ${PROJECT_NAME}-targets @@ -38,11 +43,6 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" TYPE INCLUDE) - configure_file("${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.pc.cmakein" - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY) - install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" - DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") - install(EXPORT ${PROJECT_NAME}-targets NAMESPACE "${_namespace}::" DESTINATION "${CMAKE_INSTALL_CMAKEBASEDIR}") From 1fe0d0b9ad8dab15da0e85b1619d0ae8f316131e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Wed, 25 Sep 2019 09:54:18 +0200 Subject: [PATCH 25/75] fix compile and test failures on FreeBSD 11.3 --- test/epoll-test.c | 4 +++- test/kqueue-state.c | 2 ++ test/many-timers.c | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/epoll-test.c b/test/epoll-test.c index 24bc7f9..1a33f99 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -506,7 +506,8 @@ test11() int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); if (fd < 0) { - return -1; + // Don't fail the test when there is no graphics card. + goto out; } struct epoll_event event; @@ -527,6 +528,7 @@ test11() } close(fd); +out: close(ep); return 0; } diff --git a/test/kqueue-state.c b/test/kqueue-state.c index 1dc725f..0df61ec 100644 --- a/test/kqueue-state.c +++ b/test/kqueue-state.c @@ -1,3 +1,5 @@ +#include + #include #include diff --git a/test/many-timers.c b/test/many-timers.c index 0c19999..c101a7a 100644 --- a/test/many-timers.c +++ b/test/many-timers.c @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -21,6 +23,20 @@ } \ } while (0) +// TODO(jan): Remove this once the definition is exposed in in +// all supported FreeBSD versions. +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + static bool is_fast_timer(int fd) { From 7c38c31167001e72717160f5fd7c232a2d4e433f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 13:45:34 +0100 Subject: [PATCH 26/75] implement eventfd (WIP, untested) --- .gitignore | 1 + Version.map | 4 + include/sys/eventfd.h | 39 +++++++++ include/sys/signalfd.h | 10 ++- include/sys/timerfd.h | 10 ++- src/CMakeLists.txt | 2 + src/common.c | 28 +++++- src/eventfd.c | 195 +++++++++++++++++++++++++++++++++++++++++ src/eventfd_ctx.c | 142 ++++++++++++++++++++++++++++++ src/eventfd_ctx.h | 24 +++++ 10 files changed, 445 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 include/sys/eventfd.h create mode 100644 src/eventfd.c create mode 100644 src/eventfd_ctx.c create mode 100644 src/eventfd_ctx.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5309e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build*/ diff --git a/Version.map b/Version.map index e9ed66f..27685b4 100644 --- a/Version.map +++ b/Version.map @@ -2,6 +2,7 @@ global: epoll_shim_close; epoll_shim_read; + epoll_shim_write; epoll_create; epoll_create1; epoll_ctl; @@ -9,5 +10,8 @@ signalfd; timerfd_create; timerfd_settime; + eventfd; + eventfd_read; + eventfd_write; local: *; }; diff --git a/include/sys/eventfd.h b/include/sys/eventfd.h new file mode 100644 index 0000000..a7158c0 --- /dev/null +++ b/include/sys/eventfd.h @@ -0,0 +1,39 @@ +#ifndef SHIM_SYS_EVENTFD_H +#define SHIM_SYS_EVENTFD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef uint64_t eventfd_t; + +#define EFD_SEMAPHORE 1 +#define EFD_CLOEXEC O_CLOEXEC +#define EFD_NONBLOCK O_NONBLOCK + +int eventfd(unsigned int, int); +int eventfd_read(int, eventfd_t *); +int eventfd_write(int, eventfd_t); + + +#ifndef SHIM_SYS_SHIM_HELPERS +#define SHIM_SYS_SHIM_HELPERS +#include /* IWYU pragma: keep */ + +extern int epoll_shim_close(int); +extern ssize_t epoll_shim_read(int, void *, size_t); +extern ssize_t epoll_shim_write(int, void const*, size_t); +#define close epoll_shim_close +#define read epoll_shim_read +#define write epoll_shim_write +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* sys/eventfd.h */ diff --git a/include/sys/signalfd.h b/include/sys/signalfd.h index 0a3f273..f956b19 100644 --- a/include/sys/signalfd.h +++ b/include/sys/signalfd.h @@ -45,16 +45,20 @@ struct signalfd_siginfo { uint8_t pad[128-12*4-4*8-2]; }; + #ifndef SHIM_SYS_SHIM_HELPERS #define SHIM_SYS_SHIM_HELPERS #include /* IWYU pragma: keep */ -extern int epoll_shim_close(int /*fd*/); -extern ssize_t epoll_shim_read(int /*fd*/, void * /*buf*/, size_t /*nbytes*/); -#define read epoll_shim_read +extern int epoll_shim_close(int); +extern ssize_t epoll_shim_read(int, void *, size_t); +extern ssize_t epoll_shim_write(int, void const*, size_t); #define close epoll_shim_close +#define read epoll_shim_read +#define write epoll_shim_write #endif + #ifdef __cplusplus } #endif diff --git a/include/sys/timerfd.h b/include/sys/timerfd.h index 7b84699..4458488 100644 --- a/include/sys/timerfd.h +++ b/include/sys/timerfd.h @@ -22,16 +22,20 @@ int timerfd_settime(int /*fd*/, int /*flags*/, int timerfd_gettime(int, struct itimerspec *); #endif + #ifndef SHIM_SYS_SHIM_HELPERS #define SHIM_SYS_SHIM_HELPERS #include /* IWYU pragma: keep */ -extern int epoll_shim_close(int /*fd*/); -extern ssize_t epoll_shim_read(int /*fd*/, void * /*buf*/, size_t /*nbytes*/); -#define read epoll_shim_read +extern int epoll_shim_close(int); +extern ssize_t epoll_shim_read(int, void *, size_t); +extern ssize_t epoll_shim_write(int, void const*, size_t); #define close epoll_shim_close +#define read epoll_shim_read +#define write epoll_shim_write #endif + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 02b284b..9e65ac4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ add_library(epoll-shim epoll.c timerfd.c signalfd.c + eventfd.c + eventfd_ctx.c common.c) target_link_libraries(epoll-shim PRIVATE Threads::Threads rt) target_include_directories( diff --git a/src/common.c b/src/common.c index 6c6eddc..e82b309 100644 --- a/src/common.c +++ b/src/common.c @@ -9,16 +9,24 @@ struct timerfd_context; extern pthread_mutex_t timerfd_context_mtx; extern struct timerfd_context *get_timerfd_context(int fd, bool create_new); -extern ssize_t timerfd_read( - struct timerfd_context *, void *buf, size_t nbytes); +extern ssize_t timerfd_read(struct timerfd_context *, void *buf, + size_t nbytes); extern int timerfd_close(struct timerfd_context *); extern pthread_mutex_t signalfd_context_mtx; extern struct signalfd_context *get_signalfd_context(int fd, bool create_new); -extern ssize_t signalfd_read( - struct signalfd_context *, void *buf, size_t nbytes); +extern ssize_t signalfd_read(struct signalfd_context *, void *buf, + size_t nbytes); extern int signalfd_close(struct signalfd_context *); +extern pthread_mutex_t eventfd_context_mtx; +extern struct eventfd_context *get_eventfd_context(int fd, bool create_new); +extern ssize_t eventfd_helper_read(struct eventfd_context *, void *buf, + size_t nbytes); +extern ssize_t eventfd_helper_write(struct eventfd_context *, void const *buf, + size_t nbytes); +extern int eventfd_close(struct eventfd_context *); + #define WRAP(context, return_type, call, unlock_after_call) \ if (fd >= 0) { \ pthread_mutex_lock(&context##_mtx); \ @@ -38,6 +46,7 @@ epoll_shim_close(int fd) { WRAP(timerfd_context, int, timerfd_close(ctx), true) WRAP(signalfd_context, int, signalfd_close(ctx), true) + WRAP(eventfd_context, int, eventfd_close(ctx), true) return close(fd); } @@ -47,6 +56,17 @@ epoll_shim_read(int fd, void *buf, size_t nbytes) { WRAP(timerfd_context, ssize_t, timerfd_read(ctx, buf, nbytes), false) WRAP(signalfd_context, ssize_t, signalfd_read(ctx, buf, nbytes), false) + WRAP(eventfd_context, ssize_t, eventfd_helper_read(ctx, buf, nbytes), + false) return read(fd, buf, nbytes); } + +ssize_t +epoll_shim_write(int fd, void const *buf, size_t nbytes) +{ + WRAP(eventfd_context, ssize_t, eventfd_helper_write(ctx, buf, nbytes), + false) + + return write(fd, buf, nbytes); +} diff --git a/src/eventfd.c b/src/eventfd.c new file mode 100644 index 0000000..2580b58 --- /dev/null +++ b/src/eventfd.c @@ -0,0 +1,195 @@ +#include +#undef read +#undef close + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "eventfd_ctx.h" + +struct eventfd_context { + int fd; + EventFDCtx ctx; + struct eventfd_context *next; +}; + +static struct eventfd_context *eventfd_contexts; +pthread_mutex_t eventfd_context_mtx = PTHREAD_MUTEX_INITIALIZER; + +struct eventfd_context * +get_eventfd_context(int fd, bool create_new) +{ + for (struct eventfd_context *ctx = eventfd_contexts; ctx; + ctx = ctx->next) { + if (fd == ctx->fd) { + return ctx; + } + } + + if (create_new) { + struct eventfd_context *new_ctx = + malloc(sizeof(struct eventfd_context)); + if (!new_ctx) { + return NULL; + } + new_ctx->fd = -1; + new_ctx->next = eventfd_contexts; + eventfd_contexts = new_ctx; + return new_ctx; + } + + return NULL; +} + +static int +eventfd_impl(unsigned int initval, int flags) +{ + if (flags & ~(EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK)) { + errno = EINVAL; + return -1; + } + + /* Only EFD_CLOEXEC and EFD_NONBLOCK eventfds are supported for now. */ + if ((flags & (EFD_CLOEXEC | EFD_NONBLOCK)) != + (EFD_CLOEXEC | EFD_NONBLOCK)) { + errno = EINVAL; + return -1; + } + + struct eventfd_context *ctx = get_eventfd_context(-1, true); + if (!ctx) { + errno = EMFILE; + return -1; + } + + errno_t ec; + if ((ec = eventfd_ctx_init(&ctx->ctx, initval, + flags & EFD_SEMAPHORE)) != 0) { + errno = ec; + return -1; + } + + ctx->fd = eventfd_ctx_fd(&ctx->ctx); + + return ctx->fd; +} + +int +eventfd(unsigned int initval, int flags) +{ + pthread_mutex_lock(&eventfd_context_mtx); + int ret = eventfd_impl(initval, flags); + pthread_mutex_unlock(&eventfd_context_mtx); + return ret; +} + +ssize_t +eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) +{ + EventFDCtx *efd_ctx = &ctx->ctx; + pthread_mutex_unlock(&eventfd_context_mtx); + + if (nbytes != sizeof(uint64_t)) { + errno = EINVAL; + return -1; + } + + errno_t ec; + if ((ec = eventfd_ctx_read(efd_ctx, buf)) != 0) { + errno = ec; + return -1; + } + + return (ssize_t)nbytes; +} + +ssize_t +eventfd_helper_write(struct eventfd_context *ctx, void const *buf, + size_t nbytes) +{ + EventFDCtx *efd_ctx = &ctx->ctx; + pthread_mutex_unlock(&eventfd_context_mtx); + + if (nbytes != sizeof(uint64_t)) { + errno = EINVAL; + return -1; + } + + uint64_t value; + memcpy(&value, buf, sizeof(uint64_t)); + + errno_t ec; + if ((ec = eventfd_ctx_write(efd_ctx, value)) != 0) { + errno = ec; + return -1; + } + + return (ssize_t)nbytes; +} + +int +eventfd_close(struct eventfd_context *ctx) +{ + ctx->fd = -1; + + errno_t ec; + if ((ec = eventfd_ctx_terminate(&ctx->ctx)) != 0) { + errno = ec; + return -1; + } + + return 0; +} + +int +eventfd_read(int fd, eventfd_t *value) +{ + struct eventfd_context *ctx; + + (void)pthread_mutex_lock(&eventfd_context_mtx); + ctx = get_eventfd_context(fd, false); + (void)pthread_mutex_unlock(&eventfd_context_mtx); + + if (!ctx) { + errno = EBADF; + return -1; + } + + errno_t ec; + if ((ec = eventfd_ctx_read(&ctx->ctx, value)) != 0) { + errno = ec; + return -1; + } + + return 0; +} + +int +eventfd_write(int fd, eventfd_t value) +{ + struct eventfd_context *ctx; + + (void)pthread_mutex_lock(&eventfd_context_mtx); + ctx = get_eventfd_context(fd, false); + (void)pthread_mutex_unlock(&eventfd_context_mtx); + + if (!ctx) { + errno = EBADF; + return -1; + } + + errno_t ec; + if ((ec = eventfd_ctx_write(&ctx->ctx, value)) != 0) { + errno = ec; + return -1; + } + + return 0; +} diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c new file mode 100644 index 0000000..6b1cf1f --- /dev/null +++ b/src/eventfd_ctx.c @@ -0,0 +1,142 @@ +#include "eventfd_ctx.h" + +#include +#include + +#include +#include + +#include + +static_assert(sizeof(unsigned int) < sizeof(uint64_t), ""); + +errno_t +eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, bool is_semaphore) +{ + *eventfd = (EventFDCtx){ + .kq_ = kqueue(), + .counter_ = counter, + .is_semaphore_ = is_semaphore, + }; + if (eventfd->kq_ < 0) { + return (errno); + } + + struct kevent kevs[1]; + + EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); + if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + errno_t err = errno; + close(eventfd->kq_); + return (err); + } + + if (counter > 0) { + struct kevent kevs[1]; + EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); + + if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ + NULL, 0, NULL) < 0) { + close(eventfd->kq_); + return (errno); + } + } + + return (0); +} + +errno_t +eventfd_ctx_terminate(EventFDCtx *eventfd) +{ + if (close(eventfd->kq_) < 0) { + return (errno); + } + + return (0); +} + +int +eventfd_ctx_fd(EventFDCtx *eventfd) +{ + return (eventfd->kq_); +} + +errno_t +eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value) +{ + if (value == UINT64_MAX) { + return (EINVAL); + } + + for (;;) { + uint_least64_t current_value = atomic_load(&eventfd->counter_); + + uint_least64_t new_value; + if (__builtin_add_overflow(current_value, value, &new_value) || + new_value > UINT64_MAX - 1) { + return (EAGAIN); + } + + if (atomic_compare_exchange_weak(&eventfd->counter_, + ¤t_value, new_value)) { + break; + } + } + + struct kevent kevs[1]; + EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); + + if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + return (errno); + } + + return (0); +} + +errno_t +eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) +{ + uint_least64_t current_value; + + for (;;) { + current_value = atomic_load(&eventfd->counter_); + if (current_value == 0) { + return (EAGAIN); + } + + uint_least64_t new_value = + eventfd->is_semaphore_ ? current_value - 1 : 0; + + if (new_value == 0) { + struct timespec zero_timeout = {0, 0}; + struct kevent kevs[32]; + int n; + + while ((n = kevent(eventfd->kq_, NULL, 0, kevs, + nitems(kevs), &zero_timeout)) > 0) { + } + if (n < 0) { + return (errno); + } + } + + if (atomic_compare_exchange_weak(&eventfd->counter_, + ¤t_value, new_value)) { + break; + } + + if (new_value == 0 && current_value > 0) { + struct kevent kevs[1]; + EV_SET(&kevs[0], 0, EVFILT_USER, /**/ + 0, NOTE_TRIGGER, 0, 0); + + if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ + NULL, 0, NULL) < 0) { + return (errno); + } + } + } + + *value = eventfd->is_semaphore_ ? 1 : current_value; + return (0); +} diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h new file mode 100644 index 0000000..09a251e --- /dev/null +++ b/src/eventfd_ctx.h @@ -0,0 +1,24 @@ +#ifndef EVENTFD_CTX_H_ +#define EVENTFD_CTX_H_ + +#include +#include +#include +#include + +typedef struct { + int kq_; + atomic_uint_least64_t counter_; + bool is_semaphore_; +} EventFDCtx; + +errno_t eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, + bool is_semaphore); +errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); + +int eventfd_ctx_fd(EventFDCtx *eventfd); + +errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); +errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); + +#endif From c53b9bef27a0f32b819bdbc9f03a1ed166717588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 13:46:24 +0100 Subject: [PATCH 27/75] add '.vscode' to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a5309e6..a890447 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build*/ +.vscode From dc4c71e619cb552a92359d2e09071e78bcc975cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 14:06:01 +0100 Subject: [PATCH 28/75] add some eventfd tests Adapted from: https://github.com/cloudius-systems/osv/blob/master/tests/tst-eventfd.cc --- test/CMakeLists.txt | 5 + test/tst-eventfd.c | 573 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 578 insertions(+) create mode 100644 test/tst-eventfd.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4c371be..3f7fae0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,3 +23,8 @@ add_test(NAME kqueue-state COMMAND kqueue-state) add_executable(many-timers many-timers.c) target_link_libraries(many-timers PRIVATE epoll-shim::epoll-shim) add_test(NAME many-timers COMMAND many-timers) + +add_executable(tst-eventfd tst-eventfd.c) +target_link_libraries(tst-eventfd + PRIVATE epoll-shim::epoll-shim Threads::Threads) +add_test(NAME tst-eventfd COMMAND tst-eventfd) diff --git a/test/tst-eventfd.c b/test/tst-eventfd.c new file mode 100644 index 0000000..a853623 --- /dev/null +++ b/test/tst-eventfd.c @@ -0,0 +1,573 @@ +/* clang-format off */ + +/* + * Based on: + * https://raw.githubusercontent.com/cloudius-systems/osv/master/tests/tst-eventfd.cc + * + * https://github.com/cloudius-systems/osv + * https://github.com/cloudius-systems/osv/blob/master/LICENSE + */ + +#if 0 +Copyright (C) 2013 Cloudius Systems, Ltd. + +Parts are copyright by other contributors. Please refer to copyright notices +in the individual source files, and to the git commit log, for a more accurate +list of copyright holders. + +OSv is open-source software, distributed under the 3-clause BSD license: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the Cloudius Systems, Ltd. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This project also includes source code adopted and adapted from four other +open-source projects - FreeBSD, OpenSolaris, Prex and Musl. These projects have +their own licenses. Please refer to the files documentation/LICENSE-* +for the licenses and copyright statements of these projects. +#endif + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TMP_FILE "/tmp/f1" + +#define handle_perror(msg) \ + do { perror(msg); printf("\n"); exit(EXIT_FAILURE); } while (0) + +#define handle_error(msg) \ + do { fprintf(stderr, "%s\n", msg); exit(EXIT_FAILURE); } while (0) + +int simple_test(void) +{ + int efd; + uint64_t c; + uint64_t u; + ssize_t s; + + printf("eventfd: running basic test: "); + fflush(stdout); + c = 5; + efd = eventfd(c, EFD_CLOEXEC | EFD_NONBLOCK); + if (efd == -1) { + handle_perror("eventfd"); + } + + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + if (c != u) { + handle_error("Incorrect value read."); + } + + s = read(efd, &u, sizeof(u)); + if (s < 0) { + int e = errno; + errno = e; + if (errno != EAGAIN) { + handle_error("EAGAIN expected"); + } + } else { + handle_error("read failure and EAGAIN expected"); + } + + s = write(efd, &c, sizeof(c)); + if (s != sizeof(c)) { + handle_perror("write"); + } + + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + if (c != u) { + handle_perror("Incorrect value read."); + } + + close(efd); + printf(" PASS\n"); + fflush(stdout); + return (0); +} + +int semaphore_test(void) +{ + int efd; + uint64_t c; + uint64_t u; + ssize_t s; + uint64_t i; + + printf("eventfd: Running semaphore_test: "); + fflush(stdout); + c = 5; + efd = eventfd(c, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE); + if (efd == -1) { + handle_perror("eventfd"); + } + + for (i = 0; i < c; i++) { + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + if (u != 1) { + handle_error("Semaphore read count 1 expected."); + } + } + + s = read(efd, &u, sizeof(u)); + if (s < 0) { + if (errno != EAGAIN) { + handle_error("EAGAIN expected"); + } + } else { + handle_error("read failure and EAGAIN expected"); + } + + printf(" PASS\n"); + fflush(stdout); + close(efd); + return (0); +} + +struct thread_data { + uint64_t ev_count; + int loop; + int efd; +}; + +void *thread_write(void *arg) +{ + struct thread_data *td = (struct thread_data *) arg; + ssize_t s; + int i; + + for (i = 0; i < td->loop; i++) { + s = write(td->efd, &td->ev_count, sizeof(td->ev_count)); + if (s != sizeof(td->ev_count)) { + handle_perror("write"); + } + usleep(100); + } + return (NULL); +} + +int threaded_test(void) +{ + uint64_t count[] = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, + }; + const int LOOP = 1000; + const int THREADS = sizeof(count) / sizeof(count[0]); + int efd; + pthread_t thread[THREADS]; + struct thread_data td[THREADS]; + uint64_t total; + int i; + int rc; + ssize_t s; + uint64_t u; + uint64_t v; + + printf("eventfd: running simple threaded test: "); + fflush(stdout); + efd = eventfd(0, EFD_CLOEXEC); + if (efd == -1) { + handle_perror("eventfd"); + } + + total = 0; + for (i = 0; i < THREADS; i++) { + td[i].efd = efd; + td[i].ev_count = count[i]; + td[i].loop = LOOP; + total += (count[i] * LOOP); + } + + for (i = 0; i < THREADS; i++) { + rc = pthread_create(&thread[i], NULL, thread_write, &td[i]); + if (rc != 0) { + handle_perror("pthread_create"); + } + } + + v = 0; + while (total != v) { + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + v += u; + } + + if (v != total) { + handle_error("Unexpected value read"); + } + + /* verify all threads have finished */ + for (i = 0; i < THREADS; i++) { + rc = pthread_join(thread[i], NULL); + if (rc != 0) { + handle_perror("pthread_join"); + } + } + close(efd); + printf(" PASS\n"); + fflush(stdout); + return (0); +} + +int poll_test(void) +{ + int efd; + ssize_t s; + uint64_t c; + uint64_t u; + struct pollfd pfd[2]; + int rc; + + printf("eventfd: running poll test: "); + fflush(stdout); + efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (efd == -1) { + handle_error("eventfd"); + } + + s = read(efd, &c, sizeof(c)); + if (s < 0) { + if (errno != EAGAIN) { + handle_error("EAGAIN expected"); + } + } else { + handle_error("read failure and EAGAIN expected"); + } + + /* no event count - read poll */ + pfd[0].fd = efd; + pfd[0].events = POLLIN; + rc = poll(pfd, 1, 10); + if (rc != 0) { + handle_error("expected timeout.\n"); + } + + if ((pfd[0].revents & POLLIN) != 0) { + handle_error("no read event - POLLIN must not be set"); + } + + if ((pfd[0].revents & POLLOUT) != 0) { + handle_error("write event on read fd is not expected"); + } + +#ifndef __FreeBSD__ + /* no event count - write poll */ + pfd[0].fd = efd; + pfd[0].events = POLLOUT; + rc = poll(pfd, 1, -1); + if (rc != 1 || ((pfd[0].revents & POLLOUT) == 0)) { + handle_error("expected write event."); + } + + /* combined read - write poll */ + pfd[0].fd = efd; + pfd[0].events = POLLOUT; + pfd[1].fd = efd; + pfd[1].events = POLLIN; + rc = poll(pfd, 2, -1); + if (rc == 1) { + if ((pfd[0].revents & POLLOUT) == 0) { + handle_error("expected write event"); + } + + if ((pfd[0].revents & POLLIN) != 0) { + handle_error("read event on write fd is not expected"); + } + + if (((pfd[1].revents & POLLOUT) != 0) || + ((pfd[1].revents & POLLIN) != 0) ) { + handle_error("expected no events on read fd."); + } + } else { + handle_error("one event expected."); + } +#endif + + /* write to event and check read poll */ + c = 1; + s = write(efd, &c, sizeof(c)); + if (s != sizeof(s)) { + handle_error(""); + } + +#ifndef __FreeBSD__ + pfd[0].fd = efd; + pfd[0].events = POLLOUT; + pfd[1].fd = efd; + pfd[1].events = POLLIN; + rc = poll(pfd, 2, -1); + if (rc == 2) { + if ((pfd[0].revents & POLLOUT) == 0) { + handle_error("expected write event"); + } + + if ((pfd[0].revents & POLLIN) != 0) { + handle_error("read event on write fd is not expected"); + } + + if ((pfd[1].revents & POLLOUT) != 0) { + handle_error("write event on read fd is not expected"); + } + + if ((pfd[1].revents & POLLIN) == 0) { + handle_error("expected read event"); + } + } else { + handle_error("expected two event."); + } +#endif + + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + /* max value boundary condition checking */ + c = ULLONG_MAX - 2; + s = write(efd, &c, sizeof(c)); + if (s != sizeof(c)) { + handle_perror("write"); + } + +#ifndef __FreeBSD__ + pfd[0].fd = efd; + pfd[0].events = POLLOUT; + pfd[1].fd = efd; + pfd[1].events = POLLIN; + rc = poll(pfd, 2, -1); + if (rc == 2) { + if ((pfd[0].revents & POLLOUT) == 0) { + handle_error("expected write event"); + } + + if ((pfd[0].revents & POLLIN) != 0) { + handle_error("read event on write fd is not expected"); + } + + if ((pfd[1].revents & POLLOUT) != 0) { + handle_error("write event on read fd is not expected"); + } + + if ((pfd[1].revents & POLLIN) == 0) { + handle_error("expected read event"); + } + } else { + handle_error("expected two event."); + } +#endif + + c = 2; + s = write(efd, &c, sizeof(c)); + if (s < 0) { + if (errno != EAGAIN) { + handle_error("write - expected EAGAIN"); + } + } else { + handle_error("write failure and EAGAIN expected"); + } + + c = 1; + s = write(efd, &c, sizeof(c)); + if (s != sizeof(c)) { + handle_perror("write"); + } + + pfd[0].fd = efd; + pfd[0].events = POLLOUT; + pfd[1].fd = efd; + pfd[1].events = POLLIN; + rc = poll(pfd, 2, -1); + if (rc == 1) { + if ((pfd[0].revents & POLLOUT) != 0) { + handle_error("write event not expected"); + } + + if ((pfd[0].revents & POLLIN) != 0) { + handle_error("read event on write fd is not expected"); + } + + if ((pfd[1].revents & POLLOUT) != 0) { + handle_error("write event on read fd is not expected"); + } + + if ((pfd[1].revents & POLLIN) == 0) { + handle_error("expected read event"); + } + } else { + handle_error("expected two event."); + } + + c = 1; + s = write(efd, &c, sizeof(c)); + if (s < 0) { + if (errno != EAGAIN) { + handle_error("write - expected EAGAIN"); + } + } else { + handle_error("write failure and EAGAIN expected"); + } + + s = read(efd, &u, sizeof(u)); + if (s != sizeof(u)) { + handle_perror("read"); + } + + if (u != ULLONG_MAX - 1) { + handle_error("Incorrect value read"); + } + + close(efd); + printf(" PASS\n"); + fflush(stdout); + return (0); +} + +int api_test(void) +{ + int efd; + int rc; + eventfd_t v; + eventfd_t u; + int fd; + + printf("eventfd: running API test: "); + fflush(stdout); + efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (efd == -1) { + handle_error("eventfd"); + } + + rc = eventfd_read(efd, &v); + if (rc < 0) { + if (errno != EAGAIN) { + handle_perror("eventfd_read"); + } + } + + u = 10; + rc = eventfd_write(efd, u); + if (rc < 0) { + handle_perror("eventfd_write"); + } + + rc = eventfd_read(efd, &v); + if (rc < 0) { + handle_perror("eventfd_read"); + } + + close(efd); + + /* check errors */ + rc = eventfd_read(efd, &v); + if (rc < 0) { + if (errno != EBADF) { + handle_perror("eventfd_read"); + } + } + + rc = eventfd_write(efd, u); + if (rc < 0) { + if (errno != EBADF) { + handle_perror("eventfd_write"); + } + } + + fd = creat(TMP_FILE, 0777); + if (fd < 0) { + handle_perror("open"); + } + + rc = eventfd_read(fd, &v); + if (rc < 0) { + if (errno != EBADF) { + handle_perror("eventfd_read"); + } + } + + rc = eventfd_write(fd, u); + if (rc < 0) { + if (errno != EBADF) { + handle_perror("eventfd_write"); + } + } + close(fd); + unlink(TMP_FILE); + printf(" PASS\n"); + fflush(stdout); + return (0); +} + +int main(void) +{ + simple_test(); + semaphore_test(); + // threaded_test(); + poll_test(); + api_test(); + return (0); +} From 7fd5712420b20321fae119191e7b7b1d0d1c2c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 14:51:09 +0100 Subject: [PATCH 29/75] implement blocking semantics for eventfd read() --- src/eventfd.c | 21 +++++++++++++-------- src/eventfd_ctx.c | 33 ++++++++++++++++++++++++++------- src/eventfd_ctx.h | 8 +++++--- test/tst-eventfd.c | 2 +- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/eventfd.c b/src/eventfd.c index 2580b58..b7750e5 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -56,12 +56,10 @@ eventfd_impl(unsigned int initval, int flags) return -1; } - /* Only EFD_CLOEXEC and EFD_NONBLOCK eventfds are supported for now. */ - if ((flags & (EFD_CLOEXEC | EFD_NONBLOCK)) != - (EFD_CLOEXEC | EFD_NONBLOCK)) { - errno = EINVAL; - return -1; - } + /* + * Don't check that EFD_CLOEXEC is set -- but our kqueue based eventfd + * will always be CLOEXEC. + */ struct eventfd_context *ctx = get_eventfd_context(-1, true); if (!ctx) { @@ -69,9 +67,16 @@ eventfd_impl(unsigned int initval, int flags) return -1; } + int ctx_flags = 0; + if (flags & EFD_SEMAPHORE) { + ctx_flags |= EVENTFD_CTX_FLAG_SEMAPHORE; + } + if (flags & EFD_NONBLOCK) { + ctx_flags |= EVENTFD_CTX_FLAG_NONBLOCK; + } + errno_t ec; - if ((ec = eventfd_ctx_init(&ctx->ctx, initval, - flags & EFD_SEMAPHORE)) != 0) { + if ((ec = eventfd_ctx_init(&ctx->ctx, initval, ctx_flags)) != 0) { errno = ec; return -1; } diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index 6b1cf1f..c6a2910 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -11,12 +11,17 @@ static_assert(sizeof(unsigned int) < sizeof(uint64_t), ""); errno_t -eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, bool is_semaphore) +eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags) { + if (flags & + ~(EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK)) { + return (EINVAL); + } + *eventfd = (EventFDCtx){ .kq_ = kqueue(), + .flags_ = flags, .counter_ = counter, - .is_semaphore_ = is_semaphore, }; if (eventfd->kq_ < 0) { return (errno); @@ -101,19 +106,32 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) for (;;) { current_value = atomic_load(&eventfd->counter_); if (current_value == 0) { - return (EAGAIN); + if (eventfd->flags_ & EVENTFD_CTX_FLAG_NONBLOCK) { + return (EAGAIN); + } + + struct kevent kevs[32]; + int n = kevent(eventfd->kq_, NULL, 0, /**/ + kevs, nitems(kevs), NULL); + if (n < 0) { + return (errno); + } + + continue; } uint_least64_t new_value = - eventfd->is_semaphore_ ? current_value - 1 : 0; + (eventfd->flags_ & EVENTFD_CTX_FLAG_SEMAPHORE) + ? current_value - 1 + : 0; if (new_value == 0) { struct timespec zero_timeout = {0, 0}; struct kevent kevs[32]; int n; - while ((n = kevent(eventfd->kq_, NULL, 0, kevs, - nitems(kevs), &zero_timeout)) > 0) { + while ((n = kevent(eventfd->kq_, NULL, 0, /**/ + kevs, nitems(kevs), &zero_timeout)) > 0) { } if (n < 0) { return (errno); @@ -137,6 +155,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) } } - *value = eventfd->is_semaphore_ ? 1 : current_value; + *value = + (eventfd->flags_ & EVENTFD_CTX_FLAG_SEMAPHORE) ? 1 : current_value; return (0); } diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h index 09a251e..8cec52c 100644 --- a/src/eventfd_ctx.h +++ b/src/eventfd_ctx.h @@ -6,14 +6,16 @@ #include #include +#define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) +#define EVENTFD_CTX_FLAG_NONBLOCK (1 << 1) + typedef struct { int kq_; + int flags_; atomic_uint_least64_t counter_; - bool is_semaphore_; } EventFDCtx; -errno_t eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, - bool is_semaphore); +errno_t eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags); errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); int eventfd_ctx_fd(EventFDCtx *eventfd); diff --git a/test/tst-eventfd.c b/test/tst-eventfd.c index a853623..5e9d831 100644 --- a/test/tst-eventfd.c +++ b/test/tst-eventfd.c @@ -566,7 +566,7 @@ int main(void) { simple_test(); semaphore_test(); - // threaded_test(); + threaded_test(); poll_test(); api_test(); return (0); From 103defa14304ece48c439e4901ad4f2e57df9e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 14:57:57 +0100 Subject: [PATCH 30/75] add tests for EventFDCtx --- test/CMakeLists.txt | 6 + test/eventfd-ctx-test.c | 240 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 test/eventfd-ctx-test.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3f7fae0..5d5951c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,3 +28,9 @@ add_executable(tst-eventfd tst-eventfd.c) target_link_libraries(tst-eventfd PRIVATE epoll-shim::epoll-shim Threads::Threads) add_test(NAME tst-eventfd COMMAND tst-eventfd) + +add_executable(eventfd-ctx-test eventfd-ctx-test.c) +target_link_libraries(eventfd-ctx-test PRIVATE Threads::Threads) +target_include_directories(eventfd-ctx-test + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../src") +add_test(NAME eventfd-ctx-test COMMAND eventfd-ctx-test) diff --git a/test/eventfd-ctx-test.c b/test/eventfd-ctx-test.c new file mode 100644 index 0000000..12efec4 --- /dev/null +++ b/test/eventfd-ctx-test.c @@ -0,0 +1,240 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include "eventfd_ctx.c" +#include "eventfd_ctx.h" + +#define REQUIRE(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "failed assertion: %d\n", __LINE__); \ + abort(); \ + } \ + } while (0) + +static void +tc_init_terminate(void) +{ + EventFDCtx eventfd; + + REQUIRE(/**/ + eventfd_ctx_init(&eventfd, 0, + EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + { + struct pollfd pfd = { + .fd = eventfd_ctx_fd(&eventfd), + .events = POLLIN, + }; + REQUIRE(poll(&pfd, 1, 0) == 0); + } + eventfd_ctx_terminate(&eventfd); + + REQUIRE(/**/ + eventfd_ctx_init(&eventfd, 1, + EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + { + struct pollfd pfd = { + .fd = eventfd_ctx_fd(&eventfd), + .events = POLLIN, + }; + REQUIRE(poll(&pfd, 1, 0) == 1); + REQUIRE(pfd.revents == POLLIN); + } + eventfd_ctx_terminate(&eventfd); +} + +static void +tc_simple_write(void) +{ + EventFDCtx eventfd; + + REQUIRE(eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_NONBLOCK) == 0); + { + REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX) == EINVAL); + REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX - 1) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); + REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); + + struct pollfd pfd = { + .fd = eventfd_ctx_fd(&eventfd), + .events = POLLIN, + }; + REQUIRE(poll(&pfd, 1, 0) == 1); + REQUIRE(pfd.revents == POLLIN); + + uint64_t value; + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == UINT64_MAX - 1); + + REQUIRE(poll(&pfd, 1, 0) == 0); + } + eventfd_ctx_terminate(&eventfd); +} + +static void +tc_simple_read(void) +{ + EventFDCtx eventfd; + uint64_t value; + + REQUIRE(/**/ + eventfd_ctx_init(&eventfd, 3, + EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + { + struct pollfd pfd = { + .fd = eventfd_ctx_fd(&eventfd), + .events = POLLIN, + }; + REQUIRE(poll(&pfd, 1, 0) == 1); + REQUIRE(pfd.revents == POLLIN); + + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == 1); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == 1); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == 1); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); + + REQUIRE(poll(&pfd, 1, 0) == 0); + } + eventfd_ctx_terminate(&eventfd); +} + +static void +tc_simple_write_read(void) +{ + EventFDCtx eventfd; + uint64_t value; + + REQUIRE(/**/ + eventfd_ctx_init(&eventfd, 0, + EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + { + struct pollfd pfd = { + .fd = eventfd_ctx_fd(&eventfd), + .events = POLLIN, + }; + REQUIRE(poll(&pfd, 1, 0) == 0); + + REQUIRE(eventfd_ctx_write(&eventfd, 2) == 0); + + REQUIRE(poll(&pfd, 1, 0) == 1); + REQUIRE(pfd.revents == POLLIN); + + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == 1); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(value == 1); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); + + REQUIRE(poll(&pfd, 1, 0) == 0); + } + eventfd_ctx_terminate(&eventfd); +} + +typedef struct { + EventFDCtx *eventfd; + int signal_pipe[2]; +} ReadThreadArgs; + +static atomic_int read_counter; + +static void * +read_fun(void *arg) +{ + ReadThreadArgs *args = arg; + EventFDCtx *eventfd = args->eventfd; + + for (;;) { + uint64_t value; + errno_t err; + + if ((err = eventfd_ctx_read(eventfd, &value)) == 0) { + int current_counter = + atomic_fetch_add(&read_counter, 1); + + if (current_counter % 10 == 0 && + current_counter < 100) { + REQUIRE(eventfd_ctx_write(eventfd, 10) == 0); + } + + continue; + } + + REQUIRE(err == EAGAIN); + + struct pollfd pfds[2] = {/**/ + { + .fd = eventfd_ctx_fd(eventfd), + .events = POLLIN, + }, + { + .fd = args->signal_pipe[0], + .events = POLLIN, + }}; + REQUIRE(poll(pfds, nitems(pfds), -1) > 0); + + if (pfds[1].revents) { + break; + } + } + + return (NULL); +} + +static void +tc_threads_read(void) +{ + EventFDCtx eventfd; + pthread_t threads[4]; + ReadThreadArgs thread_args[4]; + + for (int i = 0; i < 1000; ++i) { + read_counter = 0; + REQUIRE(eventfd_ctx_init(&eventfd, 0, + EVENTFD_CTX_FLAG_SEMAPHORE | + EVENTFD_CTX_FLAG_NONBLOCK) == 0); + + uint64_t counter_val = 100; + + for (int i = 0; i < (int)nitems(threads); ++i) { + thread_args[i].eventfd = &eventfd; + REQUIRE(pipe2(thread_args[i].signal_pipe, + O_CLOEXEC | O_NONBLOCK) == 0); + REQUIRE(pthread_create(&threads[i], NULL, /**/ + read_fun, &thread_args[i]) == 0); + } + + REQUIRE(eventfd_ctx_write(&eventfd, counter_val) == 0); + + while (atomic_load(&read_counter) != 2 * (int)counter_val) { + } + + for (int i = 0; i < (int)nitems(threads); ++i) { + REQUIRE(close(thread_args[i].signal_pipe[1]) == 0); + REQUIRE(pthread_join(threads[i], NULL) == 0); + REQUIRE(close(thread_args[i].signal_pipe[0]) == 0); + } + + eventfd_ctx_terminate(&eventfd); + REQUIRE(read_counter == 2 * counter_val); + } +} + +int +main() +{ + tc_init_terminate(); + tc_simple_write(); + tc_simple_read(); + tc_simple_write_read(); + tc_threads_read(); +} From 65e1d541ca2d7ef5c5b1f9222500c87396c38a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 14:58:54 +0100 Subject: [PATCH 31/75] fix include order --- test/eventfd-ctx-test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/eventfd-ctx-test.c b/test/eventfd-ctx-test.c index 12efec4..c930348 100644 --- a/test/eventfd-ctx-test.c +++ b/test/eventfd-ctx-test.c @@ -8,9 +8,10 @@ #include #include -#include "eventfd_ctx.c" #include "eventfd_ctx.h" +#include "eventfd_ctx.c" + #define REQUIRE(x) \ do { \ if (!(x)) { \ From 31e0955eb327eb201e30508a265d8b1990d579a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 15:20:49 +0100 Subject: [PATCH 32/75] fix racy update of 'current_expirations' of timerfd_context --- src/timerfd.c | 54 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/timerfd.c b/src/timerfd.c index 77e93fc..e48a8ca 100644 --- a/src/timerfd.c +++ b/src/timerfd.c @@ -342,6 +342,7 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) { int fd = ctx->fd; int flags = ctx->flags; + enum timerfd_kind kind = ctx->kind; pthread_mutex_unlock(&timerfd_context_mtx); if (nbytes < sizeof(uint64_t)) { @@ -349,27 +350,44 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) return -1; } - struct timespec timeout = {0, 0}; - struct kevent kev; - int ret = kevent(fd, NULL, 0, &kev, 1, - (flags & TFD_NONBLOCK) ? &timeout : NULL); - if (ret < 0) { - return -1; - } + uint64_t nr_expired; + for (;;) { + struct timespec timeout = {0, 0}; + struct kevent kev; + int ret = kevent(fd, NULL, 0, &kev, 1, + (flags & TFD_NONBLOCK) ? &timeout : NULL); + if (ret < 0) { + return -1; + } - if (ret == 0) { - errno = EAGAIN; - return -1; - } + if (ret == 0) { + errno = EAGAIN; + return -1; + } - uint64_t nr_expired; - if (ctx->kind == TIMERFD_KIND_COMPLEX) { - uint64_t expired_new = (uint64_t)kev.udata; - nr_expired = expired_new - ctx->complex.current_expirations; - ctx->complex.current_expirations = expired_new; - } else { - nr_expired = kev.data; + if (kind == TIMERFD_KIND_COMPLEX) { + uint64_t expired_new = (uint64_t)kev.udata; + + /* TODO(jan): Should replace this with a + * per-timerfd_context mutex. */ + pthread_mutex_lock(&timerfd_context_mtx); + if (expired_new > ctx->complex.current_expirations) { + nr_expired = expired_new - + ctx->complex.current_expirations; + ctx->complex.current_expirations = expired_new; + } else { + nr_expired = 0; + } + pthread_mutex_unlock(&timerfd_context_mtx); + } else { + nr_expired = kev.data; + } + + if (nr_expired != 0) { + break; + } } + memcpy(buf, &nr_expired, sizeof(uint64_t)); return sizeof(uint64_t); From 0c033bfb0f1d392a0378b834c07550ff1d15b6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 15:28:33 +0100 Subject: [PATCH 33/75] mention eventfd in documentation --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b04d582..685ea8c 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ can be used to verify proper epoll behavior. However, this library contains some very ugly hacks and workarounds. For example: - - When using timerfd or signalfd, `read` and `close` are redefined as macros - to internal helper functions. This is needed as there is some internal - context that has to be free'd properly. This means that you shouldn't create - a timerfd/signalfd in one part of a program and close it in a different part - where `sys/timerfd.h` isn't included. The context would leak. Luckily, - software such as `libinput` behaves very nicely and puts all timerfd related - code in a single source file. + - When using timerfd, signalfd or eventfd, `read`, `write` and `close` are + redefined as macros to internal helper functions. This is needed as there + is some internal context that has to be free'd properly. This means that + you shouldn't create a timerfd/signalfd in one part of a program and close + it in a different part where `sys/timerfd.h` isn't included. The context + would leak. Luckily, software such as `libinput` behaves very nicely and + puts all timerfd related code in a single source file. - There is exactly one static int reserved for fds that can be polled but are not supported by kqueue under FreeBSD. This includes graphics or sound devices under `/dev`. You can only have one of them throughout all epoll From 63911776d894d1ffbbe680ac6469ac90fe624df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 15:56:11 +0100 Subject: [PATCH 34/75] forward declare eventfd_context --- src/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common.c b/src/common.c index e82b309..0c7da1c 100644 --- a/src/common.c +++ b/src/common.c @@ -6,6 +6,7 @@ struct signalfd_context; struct timerfd_context; +struct eventfd_context; extern pthread_mutex_t timerfd_context_mtx; extern struct timerfd_context *get_timerfd_context(int fd, bool create_new); From 8b947462ed017ce6f44d4e598c38fb6153216270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 16:47:15 +0100 Subject: [PATCH 35/75] split up shim helper include guards so that every fd type gets what it needs --- include/sys/epoll.h | 9 +++++++++ include/sys/eventfd.h | 12 ++++++++++-- include/sys/signalfd.h | 8 +++++--- include/sys/timerfd.h | 8 +++++--- src/eventfd.c | 1 + 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/include/sys/epoll.h b/include/sys/epoll.h index 4894d89..7d3af4c 100644 --- a/include/sys/epoll.h +++ b/include/sys/epoll.h @@ -67,6 +67,15 @@ int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); #endif +#ifndef SHIM_SYS_SHIM_HELPERS +#define SHIM_SYS_SHIM_HELPERS +#include /* IWYU pragma: keep */ + +extern int epoll_shim_close(int); +#define close epoll_shim_close +#endif + + #ifdef __cplusplus } #endif diff --git a/include/sys/eventfd.h b/include/sys/eventfd.h index a7158c0..6cb5426 100644 --- a/include/sys/eventfd.h +++ b/include/sys/eventfd.h @@ -24,10 +24,18 @@ int eventfd_write(int, eventfd_t); #include /* IWYU pragma: keep */ extern int epoll_shim_close(int); -extern ssize_t epoll_shim_read(int, void *, size_t); -extern ssize_t epoll_shim_write(int, void const*, size_t); #define close epoll_shim_close +#endif + +#ifndef SHIM_SYS_SHIM_HELPERS_READ +#define SHIM_SYS_SHIM_HELPERS_READ +extern ssize_t epoll_shim_read(int, void *, size_t); #define read epoll_shim_read +#endif + +#ifndef SHIM_SYS_SHIM_HELPERS_WRITE +#define SHIM_SYS_SHIM_HELPERS_WRITE +extern ssize_t epoll_shim_write(int, void const*, size_t); #define write epoll_shim_write #endif diff --git a/include/sys/signalfd.h b/include/sys/signalfd.h index f956b19..162fae5 100644 --- a/include/sys/signalfd.h +++ b/include/sys/signalfd.h @@ -51,11 +51,13 @@ struct signalfd_siginfo { #include /* IWYU pragma: keep */ extern int epoll_shim_close(int); -extern ssize_t epoll_shim_read(int, void *, size_t); -extern ssize_t epoll_shim_write(int, void const*, size_t); #define close epoll_shim_close +#endif + +#ifndef SHIM_SYS_SHIM_HELPERS_READ +#define SHIM_SYS_SHIM_HELPERS_READ +extern ssize_t epoll_shim_read(int, void *, size_t); #define read epoll_shim_read -#define write epoll_shim_write #endif diff --git a/include/sys/timerfd.h b/include/sys/timerfd.h index 4458488..f5e7797 100644 --- a/include/sys/timerfd.h +++ b/include/sys/timerfd.h @@ -28,11 +28,13 @@ int timerfd_gettime(int, struct itimerspec *); #include /* IWYU pragma: keep */ extern int epoll_shim_close(int); -extern ssize_t epoll_shim_read(int, void *, size_t); -extern ssize_t epoll_shim_write(int, void const*, size_t); #define close epoll_shim_close +#endif + +#ifndef SHIM_SYS_SHIM_HELPERS_READ +#define SHIM_SYS_SHIM_HELPERS_READ +extern ssize_t epoll_shim_read(int, void *, size_t); #define read epoll_shim_read -#define write epoll_shim_write #endif diff --git a/src/eventfd.c b/src/eventfd.c index b7750e5..73be7d5 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -1,5 +1,6 @@ #include #undef read +#undef write #undef close #include From 0c54d2ef3c1c71853d15bc501da855ca2f97b395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 1 Nov 2019 21:58:46 +0100 Subject: [PATCH 36/75] move blocking logic out of EventFDCtx --- src/eventfd.c | 34 +++++++++++++++++++++++++++++----- src/eventfd_ctx.c | 16 ++-------------- src/eventfd_ctx.h | 1 - test/eventfd-ctx-test.c | 17 ++++++----------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/eventfd.c b/src/eventfd.c index 73be7d5..1e5cf8c 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -5,6 +5,9 @@ #include +#include +#include + #include #include #include @@ -17,6 +20,7 @@ struct eventfd_context { int fd; + int flags; EventFDCtx ctx; struct eventfd_context *next; }; @@ -72,9 +76,6 @@ eventfd_impl(unsigned int initval, int flags) if (flags & EFD_SEMAPHORE) { ctx_flags |= EVENTFD_CTX_FLAG_SEMAPHORE; } - if (flags & EFD_NONBLOCK) { - ctx_flags |= EVENTFD_CTX_FLAG_NONBLOCK; - } errno_t ec; if ((ec = eventfd_ctx_init(&ctx->ctx, initval, ctx_flags)) != 0) { @@ -83,6 +84,7 @@ eventfd_impl(unsigned int initval, int flags) } ctx->fd = eventfd_ctx_fd(&ctx->ctx); + ctx->flags = flags; return ctx->fd; } @@ -96,10 +98,30 @@ eventfd(unsigned int initval, int flags) return ret; } +static errno_t +eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, + bool nonblock) +{ + for (;;) { + errno_t ec = eventfd_ctx_read(eventfd_ctx, value); + if (nonblock || ec != EAGAIN) { + return (ec); + } + + struct kevent kevs[32]; + int n = kevent(eventfd_ctx->kq_, NULL, 0, /**/ + kevs, nitems(kevs), NULL); + if (n < 0) { + return (errno); + } + } +} + ssize_t eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) { EventFDCtx *efd_ctx = &ctx->ctx; + int flags = ctx->flags; pthread_mutex_unlock(&eventfd_context_mtx); if (nbytes != sizeof(uint64_t)) { @@ -108,7 +130,8 @@ eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) } errno_t ec; - if ((ec = eventfd_ctx_read(efd_ctx, buf)) != 0) { + if ((ec = eventfd_ctx_read_or_block(efd_ctx, buf, + flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; } @@ -169,7 +192,8 @@ eventfd_read(int fd, eventfd_t *value) } errno_t ec; - if ((ec = eventfd_ctx_read(&ctx->ctx, value)) != 0) { + if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, value, + ctx->flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; } diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index c6a2910..8825272 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -13,8 +13,7 @@ static_assert(sizeof(unsigned int) < sizeof(uint64_t), ""); errno_t eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags) { - if (flags & - ~(EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK)) { + if (flags & ~(EVENTFD_CTX_FLAG_SEMAPHORE)) { return (EINVAL); } @@ -106,18 +105,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) for (;;) { current_value = atomic_load(&eventfd->counter_); if (current_value == 0) { - if (eventfd->flags_ & EVENTFD_CTX_FLAG_NONBLOCK) { - return (EAGAIN); - } - - struct kevent kevs[32]; - int n = kevent(eventfd->kq_, NULL, 0, /**/ - kevs, nitems(kevs), NULL); - if (n < 0) { - return (errno); - } - - continue; + return (EAGAIN); } uint_least64_t new_value = diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h index 8cec52c..f1638b9 100644 --- a/src/eventfd_ctx.h +++ b/src/eventfd_ctx.h @@ -7,7 +7,6 @@ #include #define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) -#define EVENTFD_CTX_FLAG_NONBLOCK (1 << 1) typedef struct { int kq_; diff --git a/test/eventfd-ctx-test.c b/test/eventfd-ctx-test.c index c930348..41d78e3 100644 --- a/test/eventfd-ctx-test.c +++ b/test/eventfd-ctx-test.c @@ -26,8 +26,7 @@ tc_init_terminate(void) EventFDCtx eventfd; REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 0, - EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { struct pollfd pfd = { .fd = eventfd_ctx_fd(&eventfd), @@ -38,8 +37,7 @@ tc_init_terminate(void) eventfd_ctx_terminate(&eventfd); REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 1, - EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + eventfd_ctx_init(&eventfd, 1, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { struct pollfd pfd = { .fd = eventfd_ctx_fd(&eventfd), @@ -56,7 +54,7 @@ tc_simple_write(void) { EventFDCtx eventfd; - REQUIRE(eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_NONBLOCK) == 0); + REQUIRE(eventfd_ctx_init(&eventfd, 0, 0) == 0); { REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX) == EINVAL); REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX - 1) == 0); @@ -86,8 +84,7 @@ tc_simple_read(void) uint64_t value; REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 3, - EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + eventfd_ctx_init(&eventfd, 3, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { struct pollfd pfd = { .fd = eventfd_ctx_fd(&eventfd), @@ -116,8 +113,7 @@ tc_simple_write_read(void) uint64_t value; REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 0, - EVENTFD_CTX_FLAG_SEMAPHORE | EVENTFD_CTX_FLAG_NONBLOCK) == 0); + eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { struct pollfd pfd = { .fd = eventfd_ctx_fd(&eventfd), @@ -201,8 +197,7 @@ tc_threads_read(void) for (int i = 0; i < 1000; ++i) { read_counter = 0; REQUIRE(eventfd_ctx_init(&eventfd, 0, - EVENTFD_CTX_FLAG_SEMAPHORE | - EVENTFD_CTX_FLAG_NONBLOCK) == 0); + EVENTFD_CTX_FLAG_SEMAPHORE) == 0); uint64_t counter_val = 100; From 80eaa39cf0ae75f353ed398e07f5601c32328eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 15:30:11 +0100 Subject: [PATCH 37/75] use poll to wait for eventfd --- src/eventfd.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/eventfd.c b/src/eventfd.c index 1e5cf8c..d78739a 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -108,10 +109,8 @@ eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, return (ec); } - struct kevent kevs[32]; - int n = kevent(eventfd_ctx->kq_, NULL, 0, /**/ - kevs, nitems(kevs), NULL); - if (n < 0) { + struct pollfd pfd = {.fd = eventfd_ctx->kq_, .events = POLLIN}; + if (poll(&pfd, 1, -1) < 0) { return (errno); } } From 31ab9b2db9a5877e8ec252759392508e2fbba8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 16:07:16 +0100 Subject: [PATCH 38/75] eventfd: take kqueue fd as argument instead of creating it --- src/eventfd.c | 33 +++++++---- src/eventfd_ctx.c | 32 +++-------- src/eventfd_ctx.h | 8 +-- test/eventfd-ctx-test.c | 122 ++++++++++++++++++++-------------------- 4 files changed, 95 insertions(+), 100 deletions(-) diff --git a/src/eventfd.c b/src/eventfd.c index d78739a..d6468d5 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -73,18 +73,23 @@ eventfd_impl(unsigned int initval, int flags) return -1; } + ctx->fd = kqueue(); + if (ctx->fd < 0) { + return -1; + } + int ctx_flags = 0; if (flags & EFD_SEMAPHORE) { ctx_flags |= EVENTFD_CTX_FLAG_SEMAPHORE; } errno_t ec; - if ((ec = eventfd_ctx_init(&ctx->ctx, initval, ctx_flags)) != 0) { + if ((ec = eventfd_ctx_init(&ctx->ctx, /**/ + ctx->fd, initval, ctx_flags)) != 0) { errno = ec; return -1; } - ctx->fd = eventfd_ctx_fd(&ctx->ctx); ctx->flags = flags; return ctx->fd; @@ -100,16 +105,16 @@ eventfd(unsigned int initval, int flags) } static errno_t -eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, +eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, int kq, uint64_t *value, bool nonblock) { for (;;) { - errno_t ec = eventfd_ctx_read(eventfd_ctx, value); + errno_t ec = eventfd_ctx_read(eventfd_ctx, kq, value); if (nonblock || ec != EAGAIN) { return (ec); } - struct pollfd pfd = {.fd = eventfd_ctx->kq_, .events = POLLIN}; + struct pollfd pfd = {.fd = kq, .events = POLLIN}; if (poll(&pfd, 1, -1) < 0) { return (errno); } @@ -119,6 +124,7 @@ eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, ssize_t eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) { + int fd = ctx->fd; EventFDCtx *efd_ctx = &ctx->ctx; int flags = ctx->flags; pthread_mutex_unlock(&eventfd_context_mtx); @@ -129,7 +135,7 @@ eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) } errno_t ec; - if ((ec = eventfd_ctx_read_or_block(efd_ctx, buf, + if ((ec = eventfd_ctx_read_or_block(efd_ctx, fd, buf, flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; @@ -142,6 +148,7 @@ ssize_t eventfd_helper_write(struct eventfd_context *ctx, void const *buf, size_t nbytes) { + int fd = ctx->fd; EventFDCtx *efd_ctx = &ctx->ctx; pthread_mutex_unlock(&eventfd_context_mtx); @@ -154,7 +161,7 @@ eventfd_helper_write(struct eventfd_context *ctx, void const *buf, memcpy(&value, buf, sizeof(uint64_t)); errno_t ec; - if ((ec = eventfd_ctx_write(efd_ctx, value)) != 0) { + if ((ec = eventfd_ctx_write(efd_ctx, fd, value)) != 0) { errno = ec; return -1; } @@ -165,10 +172,14 @@ eventfd_helper_write(struct eventfd_context *ctx, void const *buf, int eventfd_close(struct eventfd_context *ctx) { + errno_t ec = eventfd_ctx_terminate(&ctx->ctx); + + if (close(ctx->fd) < 0) { + ec = ec ? ec : errno; + } ctx->fd = -1; - errno_t ec; - if ((ec = eventfd_ctx_terminate(&ctx->ctx)) != 0) { + if (ec) { errno = ec; return -1; } @@ -191,7 +202,7 @@ eventfd_read(int fd, eventfd_t *value) } errno_t ec; - if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, value, + if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, fd, value, ctx->flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; @@ -215,7 +226,7 @@ eventfd_write(int fd, eventfd_t value) } errno_t ec; - if ((ec = eventfd_ctx_write(&ctx->ctx, value)) != 0) { + if ((ec = eventfd_ctx_write(&ctx->ctx, fd, value)) != 0) { errno = ec; return -1; } diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index 8825272..98cd7ae 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -11,27 +11,22 @@ static_assert(sizeof(unsigned int) < sizeof(uint64_t), ""); errno_t -eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags) +eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags) { if (flags & ~(EVENTFD_CTX_FLAG_SEMAPHORE)) { return (EINVAL); } *eventfd = (EventFDCtx){ - .kq_ = kqueue(), .flags_ = flags, .counter_ = counter, }; - if (eventfd->kq_ < 0) { - return (errno); - } struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); - if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + if (kevent(kq, kevs, nitems(kevs), NULL, 0, NULL) < 0) { errno_t err = errno; - close(eventfd->kq_); return (err); } @@ -39,9 +34,8 @@ eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags) struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); - if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ + if (kevent(kq, kevs, nitems(kevs), /**/ NULL, 0, NULL) < 0) { - close(eventfd->kq_); return (errno); } } @@ -52,21 +46,11 @@ eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags) errno_t eventfd_ctx_terminate(EventFDCtx *eventfd) { - if (close(eventfd->kq_) < 0) { - return (errno); - } - return (0); } -int -eventfd_ctx_fd(EventFDCtx *eventfd) -{ - return (eventfd->kq_); -} - errno_t -eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value) +eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value) { if (value == UINT64_MAX) { return (EINVAL); @@ -90,7 +74,7 @@ eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value) struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); - if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + if (kevent(kq, kevs, nitems(kevs), NULL, 0, NULL) < 0) { return (errno); } @@ -98,7 +82,7 @@ eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value) } errno_t -eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) +eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value) { uint_least64_t current_value; @@ -118,7 +102,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) struct kevent kevs[32]; int n; - while ((n = kevent(eventfd->kq_, NULL, 0, /**/ + while ((n = kevent(kq, NULL, 0, /**/ kevs, nitems(kevs), &zero_timeout)) > 0) { } if (n < 0) { @@ -136,7 +120,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) EV_SET(&kevs[0], 0, EVFILT_USER, /**/ 0, NOTE_TRIGGER, 0, 0); - if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ + if (kevent(kq, kevs, nitems(kevs), /**/ NULL, 0, NULL) < 0) { return (errno); } diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h index f1638b9..267e8f4 100644 --- a/src/eventfd_ctx.h +++ b/src/eventfd_ctx.h @@ -9,17 +9,17 @@ #define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) typedef struct { - int kq_; int flags_; atomic_uint_least64_t counter_; } EventFDCtx; -errno_t eventfd_ctx_init(EventFDCtx *eventfd, unsigned int counter, int flags); +errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, + int flags); errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); int eventfd_ctx_fd(EventFDCtx *eventfd); -errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); -errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); +errno_t eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value); +errno_t eventfd_ctx_read(EventFDCtx *eventfd, int kq_, uint64_t *value); #endif diff --git a/test/eventfd-ctx-test.c b/test/eventfd-ctx-test.c index 41d78e3..c17898d 100644 --- a/test/eventfd-ctx-test.c +++ b/test/eventfd-ctx-test.c @@ -23,121 +23,121 @@ static void tc_init_terminate(void) { + int kq; EventFDCtx eventfd; - REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 0, + EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { - struct pollfd pfd = { - .fd = eventfd_ctx_fd(&eventfd), - .events = POLLIN, - }; + struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 0); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); - REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 1, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 1, + EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { - struct pollfd pfd = { - .fd = eventfd_ctx_fd(&eventfd), - .events = POLLIN, - }; + struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); } static void tc_simple_write(void) { + int kq; EventFDCtx eventfd; - REQUIRE(eventfd_ctx_init(&eventfd, 0, 0) == 0); + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 0, 0) == 0); { - REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX) == EINVAL); - REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX - 1) == 0); - REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); - REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); - - struct pollfd pfd = { - .fd = eventfd_ctx_fd(&eventfd), - .events = POLLIN, - }; + REQUIRE(eventfd_ctx_write(&eventfd, kq, UINT64_MAX) == EINVAL); + REQUIRE(eventfd_ctx_write(&eventfd, kq, UINT64_MAX - 1) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, kq, 1) == EAGAIN); + REQUIRE(eventfd_ctx_write(&eventfd, kq, 1) == EAGAIN); + + struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); uint64_t value; - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == UINT64_MAX - 1); REQUIRE(poll(&pfd, 1, 0) == 0); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); } static void tc_simple_read(void) { + int kq; EventFDCtx eventfd; uint64_t value; - REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 3, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 3, + EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { - struct pollfd pfd = { - .fd = eventfd_ctx_fd(&eventfd), - .events = POLLIN, - }; + struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == EAGAIN); REQUIRE(poll(&pfd, 1, 0) == 0); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); } static void tc_simple_write_read(void) { + int kq; EventFDCtx eventfd; uint64_t value; - REQUIRE(/**/ - eventfd_ctx_init(&eventfd, 0, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 0, + EVENTFD_CTX_FLAG_SEMAPHORE) == 0); { - struct pollfd pfd = { - .fd = eventfd_ctx_fd(&eventfd), - .events = POLLIN, - }; + struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 0); - REQUIRE(eventfd_ctx_write(&eventfd, 2) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, kq, 2) == 0); REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); + REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == EAGAIN); REQUIRE(poll(&pfd, 1, 0) == 0); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); } typedef struct { + int *kq; EventFDCtx *eventfd; int signal_pipe[2]; } ReadThreadArgs; @@ -149,18 +149,20 @@ read_fun(void *arg) { ReadThreadArgs *args = arg; EventFDCtx *eventfd = args->eventfd; + int kq = *args->kq; for (;;) { uint64_t value; errno_t err; - if ((err = eventfd_ctx_read(eventfd, &value)) == 0) { + if ((err = eventfd_ctx_read(eventfd, kq, &value)) == 0) { int current_counter = atomic_fetch_add(&read_counter, 1); if (current_counter % 10 == 0 && current_counter < 100) { - REQUIRE(eventfd_ctx_write(eventfd, 10) == 0); + REQUIRE(eventfd_ctx_write(eventfd, /**/ + kq, 10) == 0); } continue; @@ -169,14 +171,8 @@ read_fun(void *arg) REQUIRE(err == EAGAIN); struct pollfd pfds[2] = {/**/ - { - .fd = eventfd_ctx_fd(eventfd), - .events = POLLIN, - }, - { - .fd = args->signal_pipe[0], - .events = POLLIN, - }}; + {.fd = kq, .events = POLLIN}, + {.fd = args->signal_pipe[0], .events = POLLIN}}; REQUIRE(poll(pfds, nitems(pfds), -1) > 0); if (pfds[1].revents) { @@ -190,18 +186,21 @@ read_fun(void *arg) static void tc_threads_read(void) { + int kq; EventFDCtx eventfd; pthread_t threads[4]; ReadThreadArgs thread_args[4]; for (int i = 0; i < 1000; ++i) { read_counter = 0; - REQUIRE(eventfd_ctx_init(&eventfd, 0, + REQUIRE((kq = kqueue()) >= 0); + REQUIRE(eventfd_ctx_init(&eventfd, kq, 0, EVENTFD_CTX_FLAG_SEMAPHORE) == 0); uint64_t counter_val = 100; for (int i = 0; i < (int)nitems(threads); ++i) { + thread_args[i].kq = &kq; thread_args[i].eventfd = &eventfd; REQUIRE(pipe2(thread_args[i].signal_pipe, O_CLOEXEC | O_NONBLOCK) == 0); @@ -209,7 +208,7 @@ tc_threads_read(void) read_fun, &thread_args[i]) == 0); } - REQUIRE(eventfd_ctx_write(&eventfd, counter_val) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, kq, counter_val) == 0); while (atomic_load(&read_counter) != 2 * (int)counter_val) { } @@ -220,7 +219,8 @@ tc_threads_read(void) REQUIRE(close(thread_args[i].signal_pipe[0]) == 0); } - eventfd_ctx_terminate(&eventfd); + REQUIRE(eventfd_ctx_terminate(&eventfd) == 0); + REQUIRE(close(kq) == 0); REQUIRE(read_counter == 2 * counter_val); } } From 675602854ae60f0aa7a6be439c5858a470a9dbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 17:30:48 +0100 Subject: [PATCH 39/75] split out timerfd logic into separate 'TimerFDCtx' --- src/CMakeLists.txt | 1 + src/eventfd_ctx.h | 4 +- src/timerfd.c | 269 ++++++++------------------------------------- src/timerfd_ctx.c | 257 +++++++++++++++++++++++++++++++++++++++++++ src/timerfd_ctx.h | 43 ++++++++ 5 files changed, 346 insertions(+), 228 deletions(-) create mode 100644 src/timerfd_ctx.c create mode 100644 src/timerfd_ctx.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e65ac4..35b1864 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ find_package(Threads REQUIRED) add_library(epoll-shim epoll.c timerfd.c + timerfd_ctx.c signalfd.c eventfd.c eventfd_ctx.c diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h index 267e8f4..92100c2 100644 --- a/src/eventfd_ctx.h +++ b/src/eventfd_ctx.h @@ -17,9 +17,7 @@ errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags); errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); -int eventfd_ctx_fd(EventFDCtx *eventfd); - errno_t eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value); -errno_t eventfd_ctx_read(EventFDCtx *eventfd, int kq_, uint64_t *value); +errno_t eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value); #endif diff --git a/src/timerfd.c b/src/timerfd.c index e48a8ca..1b305ba 100644 --- a/src/timerfd.c +++ b/src/timerfd.c @@ -4,39 +4,24 @@ #include #include -#include +#include #include #include #include #include -#include #include #include #include #include -enum timerfd_kind { - TIMERFD_KIND_UNDETERMINED, - TIMERFD_KIND_SIMPLE, - TIMERFD_KIND_COMPLEX, -}; +#include "timerfd_ctx.h" struct timerfd_context { int fd; int flags; - enum timerfd_kind kind; - union { - struct { - struct itimerspec current_itimerspec; - } simple; - struct { - pthread_t worker; - timer_t timer; - uint64_t current_expirations; - } complex; - }; + TimerFDCtx ctx; struct timerfd_context *next; }; @@ -72,100 +57,6 @@ get_timerfd_context(int fd, bool create_new) return NULL; } -static void * -worker_function(void *arg) -{ - struct timerfd_context *ctx = arg; - - uint64_t total_expirations = 0; - - siginfo_t info; - sigset_t rt_set; - sigset_t block_set; - - sigemptyset(&rt_set); - sigaddset(&rt_set, SIGRTMIN); - sigaddset(&rt_set, SIGRTMIN + 1); - - sigfillset(&block_set); - - (void)pthread_sigmask(SIG_BLOCK, &block_set, NULL); - - struct kevent kev; - EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, - (void *)(intptr_t)pthread_getthreadid_np()); - (void)kevent(ctx->fd, &kev, 1, NULL, 0, NULL); - - for (;;) { - if (sigwaitinfo(&rt_set, &info) != SIGRTMIN) { - break; - } - total_expirations += 1 + timer_getoverrun(ctx->complex.timer); - EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, - (void *)(uintptr_t)total_expirations); - (void)kevent(ctx->fd, &kev, 1, NULL, 0, NULL); - } - - return NULL; -} - -static errno_t -upgrade_to_complex_timer(struct timerfd_context *ctx, int clockid) -{ - errno_t err; - - if (ctx->kind == TIMERFD_KIND_COMPLEX) { - return 0; - } - - if (ctx->kind == TIMERFD_KIND_SIMPLE) { - struct kevent kev[1]; - EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); - (void)kevent(ctx->fd, kev, nitems(kev), NULL, 0, NULL); - - ctx->kind = TIMERFD_KIND_UNDETERMINED; - } - - assert(ctx->kind == TIMERFD_KIND_UNDETERMINED); - - struct kevent kev; - EV_SET(&kev, 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); - if (kevent(ctx->fd, &kev, 1, NULL, 0, NULL) < 0) { - assert(errno != 0); - return errno; - } - - if ((err = pthread_create(&ctx->complex.worker, /**/ - NULL, worker_function, ctx)) != 0) { - return err; - } - - if (kevent(ctx->fd, NULL, 0, &kev, 1, NULL) < 0) { - goto f0; - } - - int tid = (int)(intptr_t)kev.udata; - - struct sigevent sigev = {.sigev_notify = SIGEV_THREAD_ID, - .sigev_signo = SIGRTMIN, - .sigev_notify_thread_id = tid}; - - if (timer_create(clockid, &sigev, &ctx->complex.timer) < 0) { - goto f0; - } - - ctx->complex.current_expirations = 0; - ctx->kind = TIMERFD_KIND_COMPLEX; - return 0; - -f0: - assert(errno != 0); - err = errno; - pthread_kill(ctx->complex.worker, SIGRTMIN + 1); - pthread_join(ctx->complex.worker, NULL); - return err; -} - static errno_t timerfd_create_impl(int clockid, int flags, int *fd) { @@ -190,20 +81,16 @@ timerfd_create_impl(int clockid, int flags, int *fd) return errno; } - ctx->flags = flags; - ctx->kind = TIMERFD_KIND_UNDETERMINED; - - if (clockid == CLOCK_REALTIME) { - if ((err = upgrade_to_complex_timer(ctx, /**/ - CLOCK_REALTIME)) != 0) { - goto f0; - } + if ((err = timerfd_ctx_init(&ctx->ctx, ctx->fd, clockid)) != 0) { + goto out; } + ctx->flags = flags; + *fd = ctx->fd; return 0; -f0: +out: close(ctx->fd); ctx->fd = -1; return err; @@ -247,66 +134,10 @@ timerfd_settime_impl(int fd, int flags, const struct itimerspec *new, return EINVAL; } - if ((flags & TFD_TIMER_ABSTIME) || - ((new->it_interval.tv_sec != 0 || new->it_interval.tv_nsec != 0) && - (new->it_interval.tv_sec != new->it_value.tv_sec || - new->it_interval.tv_nsec != new->it_value.tv_nsec))) { - if ((err = upgrade_to_complex_timer(ctx, /**/ - CLOCK_MONOTONIC)) != 0) { - return err; - } - } - - if (ctx->kind == TIMERFD_KIND_COMPLEX) { - if (timer_settime(ctx->complex.timer, - (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, /**/ - new, old) < 0) { - return errno; - } - } else { - struct kevent kev[1]; - int oneshot_flag; - int64_t micros; - - if (old) { - *old = ctx->simple.current_itimerspec; - } - - if (new->it_value.tv_sec == 0 && new->it_value.tv_nsec == 0) { - struct kevent kev[1]; - EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); - (void)kevent(ctx->fd, kev, nitems(kev), NULL, 0, NULL); - } else { - if (__builtin_mul_overflow(new->it_value.tv_sec, - 1000000, µs) || - __builtin_add_overflow(micros, - new->it_value.tv_nsec / 1000, µs)) { - return EOVERFLOW; - } - - if ((new->it_value.tv_nsec % 1000) && - __builtin_add_overflow(micros, 1, µs)) { - return EOVERFLOW; - } - - if (new->it_interval.tv_sec == 0 && - new->it_interval.tv_nsec == 0) { - oneshot_flag = EV_ONESHOT; - } else { - oneshot_flag = 0; - } - - EV_SET(&kev[0], 0, EVFILT_TIMER, EV_ADD | oneshot_flag, - NOTE_USECONDS, micros, 0); - - if (kevent(ctx->fd, kev, nitems(kev), /**/ - NULL, 0, NULL) < 0) { - return errno; - } - } - - ctx->simple.current_itimerspec = *new; - ctx->kind = TIMERFD_KIND_SIMPLE; + if ((err = timerfd_ctx_settime(&ctx->ctx, + (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, /**/ + new, old)) != 0) { + return err; } return 0; @@ -337,12 +168,25 @@ int timerfd_gettime(int fd, struct itimerspec *cur) } #endif +static errno_t +timerfd_ctx_read_or_block(TimerFDCtx *timerfd, uint64_t *value, bool nonblock) +{ + for (;;) { + errno_t ec = timerfd_ctx_read(timerfd, value); + if (nonblock || ec != EAGAIN) { + return (ec); + } + + struct pollfd pfd = {.fd = timerfd->kq, .events = POLLIN}; + if (poll(&pfd, 1, -1) < 0) { + return (errno); + } + } +} + ssize_t timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) { - int fd = ctx->fd; - int flags = ctx->flags; - enum timerfd_kind kind = ctx->kind; pthread_mutex_unlock(&timerfd_context_mtx); if (nbytes < sizeof(uint64_t)) { @@ -350,42 +194,12 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) return -1; } + errno_t err; uint64_t nr_expired; - for (;;) { - struct timespec timeout = {0, 0}; - struct kevent kev; - int ret = kevent(fd, NULL, 0, &kev, 1, - (flags & TFD_NONBLOCK) ? &timeout : NULL); - if (ret < 0) { - return -1; - } - - if (ret == 0) { - errno = EAGAIN; - return -1; - } - - if (kind == TIMERFD_KIND_COMPLEX) { - uint64_t expired_new = (uint64_t)kev.udata; - - /* TODO(jan): Should replace this with a - * per-timerfd_context mutex. */ - pthread_mutex_lock(&timerfd_context_mtx); - if (expired_new > ctx->complex.current_expirations) { - nr_expired = expired_new - - ctx->complex.current_expirations; - ctx->complex.current_expirations = expired_new; - } else { - nr_expired = 0; - } - pthread_mutex_unlock(&timerfd_context_mtx); - } else { - nr_expired = kev.data; - } - - if (nr_expired != 0) { - break; - } + if ((err = timerfd_ctx_read_or_block(&ctx->ctx, &nr_expired, + ctx->flags & TFD_NONBLOCK)) != 0) { + errno = err; + return -1; } memcpy(buf, &nr_expired, sizeof(uint64_t)); @@ -396,12 +210,17 @@ timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) int timerfd_close(struct timerfd_context *ctx) { - if (ctx->kind == TIMERFD_KIND_COMPLEX) { - timer_delete(ctx->complex.timer); - pthread_kill(ctx->complex.worker, SIGRTMIN + 1); - pthread_join(ctx->complex.worker, NULL); + errno_t ec = timerfd_ctx_terminate(&ctx->ctx); + + if (close(ctx->fd) < 0) { + ec = ec ? ec : errno; } - int ret = close(ctx->fd); ctx->fd = -1; - return ret; + + if (ec) { + errno = ec; + return -1; + } + + return 0; } diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c new file mode 100644 index 0000000..22b5dd0 --- /dev/null +++ b/src/timerfd_ctx.c @@ -0,0 +1,257 @@ +#include "timerfd_ctx.h" + +#include +#include + +#include +#include + +#include + +static void * +worker_function(void *arg) +{ + TimerFDCtx *ctx = arg; + + uint64_t total_expirations = 0; + + siginfo_t info; + sigset_t rt_set; + sigset_t block_set; + + sigemptyset(&rt_set); + sigaddset(&rt_set, SIGRTMIN); + sigaddset(&rt_set, SIGRTMIN + 1); + + sigfillset(&block_set); + + (void)pthread_sigmask(SIG_BLOCK, &block_set, NULL); + + struct kevent kev; + EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, + (void *)(intptr_t)pthread_getthreadid_np()); + (void)kevent(ctx->kq, &kev, 1, NULL, 0, NULL); + + for (;;) { + if (sigwaitinfo(&rt_set, &info) != SIGRTMIN) { + break; + } + total_expirations += 1 + timer_getoverrun(ctx->complex.timer); + EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, + (void *)(uintptr_t)total_expirations); + (void)kevent(ctx->kq, &kev, 1, NULL, 0, NULL); + } + + return NULL; +} + +static errno_t +upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) +{ + errno_t err; + + if (ctx->kind == TIMERFD_KIND_COMPLEX) { + return 0; + } + + if (ctx->kind == TIMERFD_KIND_SIMPLE) { + struct kevent kev[1]; + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); + (void)kevent(ctx->kq, kev, nitems(kev), NULL, 0, NULL); + + ctx->kind = TIMERFD_KIND_UNDETERMINED; + } + + assert(ctx->kind == TIMERFD_KIND_UNDETERMINED); + + struct kevent kev; + EV_SET(&kev, 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); + if (kevent(ctx->kq, &kev, 1, NULL, 0, NULL) < 0) { + assert(errno != 0); + return errno; + } + + if ((err = pthread_create(&ctx->complex.worker, /**/ + NULL, worker_function, ctx)) != 0) { + return err; + } + + if (kevent(ctx->kq, NULL, 0, &kev, 1, NULL) < 0) { + goto out; + } + + int tid = (int)(intptr_t)kev.udata; + + struct sigevent sigev = {.sigev_notify = SIGEV_THREAD_ID, + .sigev_signo = SIGRTMIN, + .sigev_notify_thread_id = tid}; + + if (timer_create(clockid, &sigev, &ctx->complex.timer) < 0) { + goto out; + } + + ctx->complex.current_expirations = 0; + ctx->kind = TIMERFD_KIND_COMPLEX; + return 0; + +out: + err = errno; + assert(err != 0); + pthread_kill(ctx->complex.worker, SIGRTMIN + 1); + pthread_join(ctx->complex.worker, NULL); + return err; +} + +errno_t +timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid) +{ + errno_t err; + + if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) { + return EINVAL; + } + + *timerfd = (TimerFDCtx){.kq = kq, .kind = TIMERFD_KIND_UNDETERMINED}; + + if (clockid == CLOCK_REALTIME) { + if ((err = upgrade_to_complex_timer(timerfd, /**/ + CLOCK_REALTIME)) != 0) { + return err; + } + } + + return 0; +} + +errno_t +timerfd_ctx_terminate(TimerFDCtx *timerfd) +{ + if (timerfd->kind == TIMERFD_KIND_COMPLEX) { + timer_delete(timerfd->complex.timer); + pthread_kill(timerfd->complex.worker, SIGRTMIN + 1); + pthread_join(timerfd->complex.worker, NULL); + } + + return (0); +} + +errno_t +timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, + const struct itimerspec *new, struct itimerspec *old) +{ + errno_t err; + + if (flags & ~(TIMER_ABSTIME)) { + return EINVAL; + } + + if ((flags & TIMER_ABSTIME) || + ((new->it_interval.tv_sec != 0 || new->it_interval.tv_nsec != 0) && + (new->it_interval.tv_sec != new->it_value.tv_sec || + new->it_interval.tv_nsec != new->it_value.tv_nsec))) { + if ((err = upgrade_to_complex_timer(timerfd, /**/ + CLOCK_MONOTONIC)) != 0) { + return err; + } + } + + if (timerfd->kind == TIMERFD_KIND_COMPLEX) { + if (timer_settime(timerfd->complex.timer, /**/ + flags, new, old) < 0) { + return errno; + } + } else { + struct kevent kev[1]; + int oneshot_flag; + int64_t micros; + + if (old) { + *old = timerfd->simple.current_itimerspec; + } + + if (new->it_value.tv_sec == 0 && new->it_value.tv_nsec == 0) { + struct kevent kev[1]; + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_DELETE, 0, 0, 0); + (void)kevent(timerfd->kq, kev, nitems(kev), NULL, 0, + NULL); + } else { + if (__builtin_mul_overflow(new->it_value.tv_sec, + 1000000, µs) || + __builtin_add_overflow(micros, + new->it_value.tv_nsec / 1000, µs)) { + return EOVERFLOW; + } + + if ((new->it_value.tv_nsec % 1000) && + __builtin_add_overflow(micros, 1, µs)) { + return EOVERFLOW; + } + + if (new->it_interval.tv_sec == 0 && + new->it_interval.tv_nsec == 0) { + oneshot_flag = EV_ONESHOT; + } else { + oneshot_flag = 0; + } + + EV_SET(&kev[0], 0, EVFILT_TIMER, EV_ADD | oneshot_flag, + NOTE_USECONDS, micros, 0); + + if (kevent(timerfd->kq, kev, nitems(kev), /**/ + NULL, 0, NULL) < 0) { + return errno; + } + } + + timerfd->simple.current_itimerspec = *new; + timerfd->kind = TIMERFD_KIND_SIMPLE; + } + + return 0; +} + +errno_t +timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) +{ + uint64_t nr_expired; + + for (;;) { + struct timespec timeout = {0, 0}; + struct kevent kev; + int ret = kevent(timerfd->kq, NULL, 0, &kev, 1, &timeout); + if (ret < 0) { + return errno; + } + + if (ret == 0) { + return EAGAIN; + } + + if (timerfd->kind == TIMERFD_KIND_COMPLEX) { + uint64_t expired_new = (uint64_t)kev.udata; + + /* TODO(jan): Should replace this with a + * per-timerfd_context mutex. */ + // pthread_mutex_lock(&timerfd_context_mtx); + if (expired_new > + timerfd->complex.current_expirations) { + nr_expired = expired_new - + timerfd->complex.current_expirations; + timerfd->complex.current_expirations = + expired_new; + } else { + nr_expired = 0; + } + // pthread_mutex_unlock(&timerfd_context_mtx); + } else { + nr_expired = kev.data; + } + + if (nr_expired != 0) { + break; + } + } + + *value = nr_expired; + return 0; +} diff --git a/src/timerfd_ctx.h b/src/timerfd_ctx.h new file mode 100644 index 0000000..b645816 --- /dev/null +++ b/src/timerfd_ctx.h @@ -0,0 +1,43 @@ +#ifndef TIMERFD_CTX_H_ +#define TIMERFD_CTX_H_ + +#include + +#include +#include +#include +#include + +#include + +enum timerfd_kind { + TIMERFD_KIND_UNDETERMINED, + TIMERFD_KIND_SIMPLE, + TIMERFD_KIND_COMPLEX, +}; + +typedef struct { + int kq; // non owning + int flags; + enum timerfd_kind kind; + union { + struct { + struct itimerspec current_itimerspec; + } simple; + struct { + pthread_t worker; + timer_t timer; + uint64_t current_expirations; + } complex; + }; +} TimerFDCtx; + +errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid); +errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd); + +errno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, + const struct itimerspec *new, struct itimerspec *old); + +errno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value); + +#endif From 3de9c7c0ab8a60745dec142df441763f44de6900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 17:36:44 +0100 Subject: [PATCH 40/75] save a non owning kqueue fd reference in EventFDCtx --- src/eventfd.c | 16 ++++++++-------- src/eventfd_ctx.c | 15 ++++++++------- src/eventfd_ctx.h | 5 +++-- test/eventfd-ctx-test.c | 37 +++++++++++++++++-------------------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/eventfd.c b/src/eventfd.c index d6468d5..2988c8c 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -105,16 +105,16 @@ eventfd(unsigned int initval, int flags) } static errno_t -eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, int kq, uint64_t *value, +eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, bool nonblock) { for (;;) { - errno_t ec = eventfd_ctx_read(eventfd_ctx, kq, value); + errno_t ec = eventfd_ctx_read(eventfd_ctx, value); if (nonblock || ec != EAGAIN) { return (ec); } - struct pollfd pfd = {.fd = kq, .events = POLLIN}; + struct pollfd pfd = {.fd = eventfd_ctx->kq_, .events = POLLIN}; if (poll(&pfd, 1, -1) < 0) { return (errno); } @@ -135,7 +135,7 @@ eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) } errno_t ec; - if ((ec = eventfd_ctx_read_or_block(efd_ctx, fd, buf, + if ((ec = eventfd_ctx_read_or_block(efd_ctx, buf, flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; @@ -161,12 +161,12 @@ eventfd_helper_write(struct eventfd_context *ctx, void const *buf, memcpy(&value, buf, sizeof(uint64_t)); errno_t ec; - if ((ec = eventfd_ctx_write(efd_ctx, fd, value)) != 0) { + if ((ec = eventfd_ctx_write(efd_ctx, value)) != 0) { errno = ec; return -1; } - return (ssize_t)nbytes; + return (ssize_t)sizeof(uint64_t); } int @@ -202,7 +202,7 @@ eventfd_read(int fd, eventfd_t *value) } errno_t ec; - if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, fd, value, + if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, value, ctx->flags & EFD_NONBLOCK)) != 0) { errno = ec; return -1; @@ -226,7 +226,7 @@ eventfd_write(int fd, eventfd_t value) } errno_t ec; - if ((ec = eventfd_ctx_write(&ctx->ctx, fd, value)) != 0) { + if ((ec = eventfd_ctx_write(&ctx->ctx, value)) != 0) { errno = ec; return -1; } diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index 98cd7ae..10a0ba7 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -18,6 +18,7 @@ eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags) } *eventfd = (EventFDCtx){ + .kq_ = kq, .flags_ = flags, .counter_ = counter, }; @@ -25,7 +26,7 @@ eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags) struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); - if (kevent(kq, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { errno_t err = errno; return (err); } @@ -34,7 +35,7 @@ eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags) struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); - if (kevent(kq, kevs, nitems(kevs), /**/ + if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ NULL, 0, NULL) < 0) { return (errno); } @@ -50,7 +51,7 @@ eventfd_ctx_terminate(EventFDCtx *eventfd) } errno_t -eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value) +eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value) { if (value == UINT64_MAX) { return (EINVAL); @@ -74,7 +75,7 @@ eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value) struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); - if (kevent(kq, kevs, nitems(kevs), NULL, 0, NULL) < 0) { + if (kevent(eventfd->kq_, kevs, nitems(kevs), NULL, 0, NULL) < 0) { return (errno); } @@ -82,7 +83,7 @@ eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value) } errno_t -eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value) +eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) { uint_least64_t current_value; @@ -102,7 +103,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value) struct kevent kevs[32]; int n; - while ((n = kevent(kq, NULL, 0, /**/ + while ((n = kevent(eventfd->kq_, NULL, 0, /**/ kevs, nitems(kevs), &zero_timeout)) > 0) { } if (n < 0) { @@ -120,7 +121,7 @@ eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value) EV_SET(&kevs[0], 0, EVFILT_USER, /**/ 0, NOTE_TRIGGER, 0, 0); - if (kevent(kq, kevs, nitems(kevs), /**/ + if (kevent(eventfd->kq_, kevs, nitems(kevs), /**/ NULL, 0, NULL) < 0) { return (errno); } diff --git a/src/eventfd_ctx.h b/src/eventfd_ctx.h index 92100c2..a8606a2 100644 --- a/src/eventfd_ctx.h +++ b/src/eventfd_ctx.h @@ -9,6 +9,7 @@ #define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) typedef struct { + int kq_; // non owning int flags_; atomic_uint_least64_t counter_; } EventFDCtx; @@ -17,7 +18,7 @@ errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags); errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); -errno_t eventfd_ctx_write(EventFDCtx *eventfd, int kq, uint64_t value); -errno_t eventfd_ctx_read(EventFDCtx *eventfd, int kq, uint64_t *value); +errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); +errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); #endif diff --git a/test/eventfd-ctx-test.c b/test/eventfd-ctx-test.c index c17898d..b3a56dd 100644 --- a/test/eventfd-ctx-test.c +++ b/test/eventfd-ctx-test.c @@ -57,17 +57,17 @@ tc_simple_write(void) REQUIRE((kq = kqueue()) >= 0); REQUIRE(eventfd_ctx_init(&eventfd, kq, 0, 0) == 0); { - REQUIRE(eventfd_ctx_write(&eventfd, kq, UINT64_MAX) == EINVAL); - REQUIRE(eventfd_ctx_write(&eventfd, kq, UINT64_MAX - 1) == 0); - REQUIRE(eventfd_ctx_write(&eventfd, kq, 1) == EAGAIN); - REQUIRE(eventfd_ctx_write(&eventfd, kq, 1) == EAGAIN); + REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX) == EINVAL); + REQUIRE(eventfd_ctx_write(&eventfd, UINT64_MAX - 1) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); + REQUIRE(eventfd_ctx_write(&eventfd, 1) == EAGAIN); struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); uint64_t value; - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == UINT64_MAX - 1); REQUIRE(poll(&pfd, 1, 0) == 0); @@ -91,13 +91,13 @@ tc_simple_read(void) REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == EAGAIN); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); REQUIRE(poll(&pfd, 1, 0) == 0); } @@ -119,16 +119,16 @@ tc_simple_write_read(void) struct pollfd pfd = {.fd = kq, .events = POLLIN}; REQUIRE(poll(&pfd, 1, 0) == 0); - REQUIRE(eventfd_ctx_write(&eventfd, kq, 2) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, 2) == 0); REQUIRE(poll(&pfd, 1, 0) == 1); REQUIRE(pfd.revents == POLLIN); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == 0); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == 0); REQUIRE(value == 1); - REQUIRE(eventfd_ctx_read(&eventfd, kq, &value) == EAGAIN); + REQUIRE(eventfd_ctx_read(&eventfd, &value) == EAGAIN); REQUIRE(poll(&pfd, 1, 0) == 0); } @@ -137,7 +137,6 @@ tc_simple_write_read(void) } typedef struct { - int *kq; EventFDCtx *eventfd; int signal_pipe[2]; } ReadThreadArgs; @@ -149,20 +148,19 @@ read_fun(void *arg) { ReadThreadArgs *args = arg; EventFDCtx *eventfd = args->eventfd; - int kq = *args->kq; for (;;) { uint64_t value; errno_t err; - if ((err = eventfd_ctx_read(eventfd, kq, &value)) == 0) { + if ((err = eventfd_ctx_read(eventfd, &value)) == 0) { int current_counter = atomic_fetch_add(&read_counter, 1); if (current_counter % 10 == 0 && current_counter < 100) { REQUIRE(eventfd_ctx_write(eventfd, /**/ - kq, 10) == 0); + 10) == 0); } continue; @@ -171,7 +169,7 @@ read_fun(void *arg) REQUIRE(err == EAGAIN); struct pollfd pfds[2] = {/**/ - {.fd = kq, .events = POLLIN}, + {.fd = eventfd->kq_, .events = POLLIN}, {.fd = args->signal_pipe[0], .events = POLLIN}}; REQUIRE(poll(pfds, nitems(pfds), -1) > 0); @@ -200,7 +198,6 @@ tc_threads_read(void) uint64_t counter_val = 100; for (int i = 0; i < (int)nitems(threads); ++i) { - thread_args[i].kq = &kq; thread_args[i].eventfd = &eventfd; REQUIRE(pipe2(thread_args[i].signal_pipe, O_CLOEXEC | O_NONBLOCK) == 0); @@ -208,7 +205,7 @@ tc_threads_read(void) read_fun, &thread_args[i]) == 0); } - REQUIRE(eventfd_ctx_write(&eventfd, kq, counter_val) == 0); + REQUIRE(eventfd_ctx_write(&eventfd, counter_val) == 0); while (atomic_load(&read_counter) != 2 * (int)counter_val) { } From ce72f9a0beacb7bc1cd938d0d585771560b9a05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 18:23:25 +0100 Subject: [PATCH 41/75] improve locking in TimerFDCtx --- src/timerfd_ctx.c | 97 ++++++++++++++++++++++++++++++++++------------- src/timerfd_ctx.h | 1 + 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index 22b5dd0..1a49b9d 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -48,7 +48,7 @@ worker_function(void *arg) static errno_t upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) { - errno_t err; + errno_t ec; if (ctx->kind == TIMERFD_KIND_COMPLEX) { return 0; @@ -71,9 +71,9 @@ upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) return errno; } - if ((err = pthread_create(&ctx->complex.worker, /**/ + if ((ec = pthread_create(&ctx->complex.worker, /**/ NULL, worker_function, ctx)) != 0) { - return err; + return ec; } if (kevent(ctx->kq, NULL, 0, &kev, 1, NULL) < 0) { @@ -82,9 +82,11 @@ upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) int tid = (int)(intptr_t)kev.udata; - struct sigevent sigev = {.sigev_notify = SIGEV_THREAD_ID, + struct sigevent sigev = { + .sigev_notify = SIGEV_THREAD_ID, .sigev_signo = SIGRTMIN, - .sigev_notify_thread_id = tid}; + .sigev_notify_thread_id = tid, + }; if (timer_create(clockid, &sigev, &ctx->complex.timer) < 0) { goto out; @@ -95,17 +97,17 @@ upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) return 0; out: - err = errno; - assert(err != 0); + ec = errno; + assert(ec != 0); pthread_kill(ctx->complex.worker, SIGRTMIN + 1); pthread_join(ctx->complex.worker, NULL); - return err; + return ec; } errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid) { - errno_t err; + errno_t ec; if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) { return EINVAL; @@ -113,10 +115,15 @@ timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid) *timerfd = (TimerFDCtx){.kq = kq, .kind = TIMERFD_KIND_UNDETERMINED}; + if ((ec = pthread_mutex_init(&timerfd->mutex, NULL)) != 0) { + return ec; + } + if (clockid == CLOCK_REALTIME) { - if ((err = upgrade_to_complex_timer(timerfd, /**/ + if ((ec = upgrade_to_complex_timer(timerfd, /**/ CLOCK_REALTIME)) != 0) { - return err; + (void)pthread_mutex_destroy(&timerfd->mutex); + return ec; } } @@ -126,20 +133,30 @@ timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid) errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd) { + errno_t ec = 0; + errno_t ec_local = 0; + if (timerfd->kind == TIMERFD_KIND_COMPLEX) { - timer_delete(timerfd->complex.timer); - pthread_kill(timerfd->complex.worker, SIGRTMIN + 1); - pthread_join(timerfd->complex.worker, NULL); + if (timer_delete(timerfd->complex.timer) < 0 && ec == 0) { + ec = errno; + } + ec_local = pthread_kill(timerfd->complex.worker, SIGRTMIN + 1); + ec = ec ? ec : ec_local; + ec_local = pthread_join(timerfd->complex.worker, NULL); + ec = ec ? ec : ec_local; } - return (0); + ec_local = pthread_mutex_destroy(&timerfd->mutex); + ec = ec ? ec : ec_local; + + return (ec); } -errno_t -timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, +static errno_t +timerfd_ctx_settime_impl(TimerFDCtx *timerfd, int flags, const struct itimerspec *new, struct itimerspec *old) { - errno_t err; + errno_t ec; if (flags & ~(TIMER_ABSTIME)) { return EINVAL; @@ -149,9 +166,9 @@ timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, ((new->it_interval.tv_sec != 0 || new->it_interval.tv_nsec != 0) && (new->it_interval.tv_sec != new->it_value.tv_sec || new->it_interval.tv_nsec != new->it_value.tv_nsec))) { - if ((err = upgrade_to_complex_timer(timerfd, /**/ + if ((ec = upgrade_to_complex_timer(timerfd, /**/ CLOCK_MONOTONIC)) != 0) { - return err; + return ec; } } @@ -211,13 +228,27 @@ timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, } errno_t -timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) +timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, + const struct itimerspec *new, struct itimerspec *old) +{ + errno_t ec; + + (void)pthread_mutex_lock(&timerfd->mutex); + ec = timerfd_ctx_settime_impl(timerfd, flags, new, old); + (void)pthread_mutex_unlock(&timerfd->mutex); + + return ec; +} + +static errno_t +timerfd_ctx_read_impl(TimerFDCtx *timerfd, uint64_t *value) { uint64_t nr_expired; for (;;) { struct timespec timeout = {0, 0}; struct kevent kev; + int ret = kevent(timerfd->kq, NULL, 0, &kev, 1, &timeout); if (ret < 0) { return errno; @@ -227,23 +258,23 @@ timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) return EAGAIN; } + nr_expired = 0; + if (timerfd->kind == TIMERFD_KIND_COMPLEX) { uint64_t expired_new = (uint64_t)kev.udata; - /* TODO(jan): Should replace this with a - * per-timerfd_context mutex. */ - // pthread_mutex_lock(&timerfd_context_mtx); + assert(expired_new && kev.filter == EVFILT_USER); + if (expired_new > timerfd->complex.current_expirations) { nr_expired = expired_new - timerfd->complex.current_expirations; timerfd->complex.current_expirations = expired_new; - } else { - nr_expired = 0; } - // pthread_mutex_unlock(&timerfd_context_mtx); } else { + assert(!kev.udata && kev.filter == EVFILT_TIMER); + nr_expired = kev.data; } @@ -255,3 +286,15 @@ timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) *value = nr_expired; return 0; } + +errno_t +timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) +{ + errno_t ec; + + (void)pthread_mutex_lock(&timerfd->mutex); + ec = timerfd_ctx_read_impl(timerfd, value); + (void)pthread_mutex_unlock(&timerfd->mutex); + + return (ec); +} diff --git a/src/timerfd_ctx.h b/src/timerfd_ctx.h index b645816..19114d1 100644 --- a/src/timerfd_ctx.h +++ b/src/timerfd_ctx.h @@ -19,6 +19,7 @@ enum timerfd_kind { typedef struct { int kq; // non owning int flags; + pthread_mutex_t mutex; enum timerfd_kind kind; union { struct { From 1ab6cde74097d2307143ebd37b7ead76f90f3de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 18:25:13 +0100 Subject: [PATCH 42/75] rename complex to complx to avoid problems with vim's syntax highlighting --- src/timerfd_ctx.c | 26 +++++++++++++------------- src/timerfd_ctx.h | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index 1a49b9d..ccfb76a 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -36,7 +36,7 @@ worker_function(void *arg) if (sigwaitinfo(&rt_set, &info) != SIGRTMIN) { break; } - total_expirations += 1 + timer_getoverrun(ctx->complex.timer); + total_expirations += 1 + timer_getoverrun(ctx->complx.timer); EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, (void *)(uintptr_t)total_expirations); (void)kevent(ctx->kq, &kev, 1, NULL, 0, NULL); @@ -71,7 +71,7 @@ upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) return errno; } - if ((ec = pthread_create(&ctx->complex.worker, /**/ + if ((ec = pthread_create(&ctx->complx.worker, /**/ NULL, worker_function, ctx)) != 0) { return ec; } @@ -88,19 +88,19 @@ upgrade_to_complex_timer(TimerFDCtx *ctx, int clockid) .sigev_notify_thread_id = tid, }; - if (timer_create(clockid, &sigev, &ctx->complex.timer) < 0) { + if (timer_create(clockid, &sigev, &ctx->complx.timer) < 0) { goto out; } - ctx->complex.current_expirations = 0; + ctx->complx.current_expirations = 0; ctx->kind = TIMERFD_KIND_COMPLEX; return 0; out: ec = errno; assert(ec != 0); - pthread_kill(ctx->complex.worker, SIGRTMIN + 1); - pthread_join(ctx->complex.worker, NULL); + pthread_kill(ctx->complx.worker, SIGRTMIN + 1); + pthread_join(ctx->complx.worker, NULL); return ec; } @@ -137,12 +137,12 @@ timerfd_ctx_terminate(TimerFDCtx *timerfd) errno_t ec_local = 0; if (timerfd->kind == TIMERFD_KIND_COMPLEX) { - if (timer_delete(timerfd->complex.timer) < 0 && ec == 0) { + if (timer_delete(timerfd->complx.timer) < 0 && ec == 0) { ec = errno; } - ec_local = pthread_kill(timerfd->complex.worker, SIGRTMIN + 1); + ec_local = pthread_kill(timerfd->complx.worker, SIGRTMIN + 1); ec = ec ? ec : ec_local; - ec_local = pthread_join(timerfd->complex.worker, NULL); + ec_local = pthread_join(timerfd->complx.worker, NULL); ec = ec ? ec : ec_local; } @@ -173,7 +173,7 @@ timerfd_ctx_settime_impl(TimerFDCtx *timerfd, int flags, } if (timerfd->kind == TIMERFD_KIND_COMPLEX) { - if (timer_settime(timerfd->complex.timer, /**/ + if (timer_settime(timerfd->complx.timer, /**/ flags, new, old) < 0) { return errno; } @@ -266,10 +266,10 @@ timerfd_ctx_read_impl(TimerFDCtx *timerfd, uint64_t *value) assert(expired_new && kev.filter == EVFILT_USER); if (expired_new > - timerfd->complex.current_expirations) { + timerfd->complx.current_expirations) { nr_expired = expired_new - - timerfd->complex.current_expirations; - timerfd->complex.current_expirations = + timerfd->complx.current_expirations; + timerfd->complx.current_expirations = expired_new; } } else { diff --git a/src/timerfd_ctx.h b/src/timerfd_ctx.h index 19114d1..ef3d78e 100644 --- a/src/timerfd_ctx.h +++ b/src/timerfd_ctx.h @@ -29,7 +29,7 @@ typedef struct { pthread_t worker; timer_t timer; uint64_t current_expirations; - } complex; + } complx; }; } TimerFDCtx; From 34a78b62f6571855e81ad4fe77f5bbb62b9f36df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 2 Nov 2019 18:31:04 +0100 Subject: [PATCH 43/75] improve consistency of kevent calls --- src/eventfd_ctx.c | 4 ++-- src/signalfd.c | 11 ++++++----- src/timerfd_ctx.c | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index 10a0ba7..eeb94a2 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -99,12 +99,12 @@ eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value) : 0; if (new_value == 0) { - struct timespec zero_timeout = {0, 0}; struct kevent kevs[32]; int n; while ((n = kevent(eventfd->kq_, NULL, 0, /**/ - kevs, nitems(kevs), &zero_timeout)) > 0) { + kevs, nitems(kevs), + &(struct timespec){0, 0})) > 0) { } if (n < 0) { return (errno); diff --git a/src/signalfd.c b/src/signalfd.c index c85d1e3..865ba3f 100644 --- a/src/signalfd.c +++ b/src/signalfd.c @@ -116,14 +116,15 @@ signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes) return -1; } - struct timespec timeout = {0, 0}; struct kevent kev; - int ret = kevent( - fd, NULL, 0, &kev, 1, (flags & SFD_NONBLOCK) ? &timeout : NULL); - if (ret == -1) { + + int n = kevent(fd, NULL, 0, &kev, 1, + (flags & SFD_NONBLOCK) ? &(struct timespec){0, 0} : NULL); + if (n < 0) { return -1; } - if (ret == 0) { + + if (n == 0) { errno = EAGAIN; return -1; } diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index ccfb76a..4575050 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -246,15 +246,15 @@ timerfd_ctx_read_impl(TimerFDCtx *timerfd, uint64_t *value) uint64_t nr_expired; for (;;) { - struct timespec timeout = {0, 0}; struct kevent kev; - int ret = kevent(timerfd->kq, NULL, 0, &kev, 1, &timeout); - if (ret < 0) { + int n = kevent(timerfd->kq, NULL, 0, &kev, 1, + &(struct timespec){0, 0}); + if (n < 0) { return errno; } - if (ret == 0) { + if (n == 0) { return EAGAIN; } From 0d6517ab1042fcf3a62a3e04851aba0b021c0aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 12:47:38 +0100 Subject: [PATCH 44/75] split out signalfd logic into SignalFDCtx --- src/CMakeLists.txt | 1 + src/signalfd.c | 69 ++++++++++++++++++++++++++++++---------------- src/signalfd_ctx.c | 57 ++++++++++++++++++++++++++++++++++++++ src/signalfd_ctx.h | 17 ++++++++++++ 4 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 src/signalfd_ctx.c create mode 100644 src/signalfd_ctx.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 35b1864..9f65656 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(epoll-shim timerfd.c timerfd_ctx.c signalfd.c + signalfd_ctx.c eventfd.c eventfd_ctx.c common.c) diff --git a/src/signalfd.c b/src/signalfd.c index 865ba3f..275d974 100644 --- a/src/signalfd.c +++ b/src/signalfd.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -15,9 +16,12 @@ #include #include +#include "signalfd_ctx.h" + struct signalfd_context { int fd; int flags; + SignalFDCtx ctx; struct signalfd_context *next; }; @@ -75,19 +79,12 @@ signalfd_impl(int fd, const sigset_t *sigs, int flags) ctx->flags = flags; - struct kevent kevs[_SIG_MAXSIG]; - int n = 0; - - for (int i = 1; i <= _SIG_MAXSIG; ++i) { - if (sigismember(sigs, i)) { - EV_SET(&kevs[n++], i, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); - } - } + errno_t err; - int ret = kevent(ctx->fd, kevs, n, NULL, 0, NULL); - if (ret < 0) { - close(ctx->fd); + if ((err = signalfd_ctx_init(&ctx->ctx, ctx->fd, sigs)) != 0) { + (void)close(ctx->fd); ctx->fd = -1; + errno = err; return -1; } @@ -103,6 +100,23 @@ signalfd(int fd, const sigset_t *sigs, int flags) return ret; } +static errno_t +signalfd_ctx_read_or_block(SignalFDCtx *signalfd_ctx, uint32_t *value, + bool nonblock) +{ + for (;;) { + errno_t ec = signalfd_ctx_read(signalfd_ctx, value); + if (nonblock || ec != EAGAIN) { + return (ec); + } + + struct pollfd pfd = {.fd = signalfd_ctx->kq, .events = POLLIN}; + if (poll(&pfd, 1, -1) < 0) { + return (errno); + } + } +} + ssize_t signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes) { @@ -116,29 +130,36 @@ signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes) return -1; } - struct kevent kev; + uint32_t signo; + errno_t err; - int n = kevent(fd, NULL, 0, &kev, 1, - (flags & SFD_NONBLOCK) ? &(struct timespec){0, 0} : NULL); - if (n < 0) { - return -1; - } - - if (n == 0) { - errno = EAGAIN; - return -1; + if ((err = signalfd_ctx_read_or_block(&ctx->ctx, &signo, + flags & SFD_NONBLOCK)) != 0) { + errno = err; + return (-1); } memset(buf, '\0', nbytes); struct signalfd_siginfo *sig_buf = buf; - sig_buf->ssi_signo = (uint32_t)kev.ident; + sig_buf->ssi_signo = signo; + return (ssize_t)nbytes; } int signalfd_close(struct signalfd_context *ctx) { - int ret = close(ctx->fd); + errno_t ec = signalfd_ctx_terminate(&ctx->ctx); + + if (close(ctx->fd) < 0) { + ec = ec ? ec : errno; + } ctx->fd = -1; - return ret; + + if (ec) { + errno = ec; + return -1; + } + + return 0; } diff --git a/src/signalfd_ctx.c b/src/signalfd_ctx.c new file mode 100644 index 0000000..b92e6f5 --- /dev/null +++ b/src/signalfd_ctx.c @@ -0,0 +1,57 @@ +#include "signalfd_ctx.h" + +#include + +#include +#include + +#include + +errno_t +signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs) +{ + *signalfd = (SignalFDCtx){.kq = kq}; + + struct kevent kevs[_SIG_MAXSIG]; + int n = 0; + + for (int i = 1; i <= _SIG_MAXSIG; ++i) { + if (sigismember(sigs, i)) { + EV_SET(&kevs[n++], i, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); + } + } + + n = kevent(signalfd->kq, kevs, n, NULL, 0, NULL); + if (n < 0) { + return errno; + } + + return 0; +} + +errno_t +signalfd_ctx_terminate(SignalFDCtx *signalfd) +{ + (void)signalfd; + + return (0); +} + +errno_t +signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident) +{ + struct kevent kev; + + int n = kevent(signalfd->kq, NULL, 0, /**/ + &kev, 1, &(struct timespec){0, 0}); + if (n < 0) { + return errno; + } + + if (n == 0) { + return EAGAIN; + } + + *ident = kev.ident; + return 0; +} diff --git a/src/signalfd_ctx.h b/src/signalfd_ctx.h new file mode 100644 index 0000000..742dd9f --- /dev/null +++ b/src/signalfd_ctx.h @@ -0,0 +1,17 @@ +#ifndef SIGNALFD_CTX_H_ +#define SIGNALFD_CTX_H_ + +#include +#include +#include + +typedef struct { + int kq; // non owning +} SignalFDCtx; + +errno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs); +errno_t signalfd_ctx_terminate(SignalFDCtx *signalfd); + +errno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident); + +#endif From da27e156f485abb7939b61ad882b399b99d3f377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 12:52:04 +0100 Subject: [PATCH 45/75] copy signalfd_siginfo out via memcpy --- src/signalfd.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/signalfd.c b/src/signalfd.c index 275d974..c6a6a93 100644 --- a/src/signalfd.c +++ b/src/signalfd.c @@ -136,14 +136,12 @@ signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes) if ((err = signalfd_ctx_read_or_block(&ctx->ctx, &signo, flags & SFD_NONBLOCK)) != 0) { errno = err; - return (-1); + return -1; } - memset(buf, '\0', nbytes); - struct signalfd_siginfo *sig_buf = buf; - sig_buf->ssi_signo = signo; - - return (ssize_t)nbytes; + struct signalfd_siginfo siginfo = {.ssi_signo = signo}; + memcpy(buf, &siginfo, sizeof(siginfo)); + return (ssize_t)sizeof(siginfo); } int From 1a5e4fc91cc8977bc0ef861da3dacf169296185a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 15:30:26 +0100 Subject: [PATCH 46/75] major refactoring of 'fd to context object' mapping --- include/sys/epoll.h | 3 + src/CMakeLists.txt | 4 +- src/common.c | 73 ------------- src/epoll_shim_ctx.c | 254 +++++++++++++++++++++++++++++++++++++++++++ src/epoll_shim_ctx.h | 70 ++++++++++++ src/eventfd.c | 226 +++++++++++++++----------------------- src/signalfd.c | 171 +++++++++++------------------ src/timerfd.c | 206 +++++++++++++---------------------- 8 files changed, 558 insertions(+), 449 deletions(-) delete mode 100644 src/common.c create mode 100644 src/epoll_shim_ctx.c create mode 100644 src/epoll_shim_ctx.h diff --git a/include/sys/epoll.h b/include/sys/epoll.h index 7d3af4c..7ee8574 100644 --- a/include/sys/epoll.h +++ b/include/sys/epoll.h @@ -67,6 +67,8 @@ int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); #endif +#ifdef notyet +#error don't include this yet #ifndef SHIM_SYS_SHIM_HELPERS #define SHIM_SYS_SHIM_HELPERS #include /* IWYU pragma: keep */ @@ -74,6 +76,7 @@ int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); extern int epoll_shim_close(int); #define close epoll_shim_close #endif +#endif #ifdef __cplusplus diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9f65656..f5ec2d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,14 +2,14 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) add_library(epoll-shim + epoll_shim_ctx.c epoll.c timerfd.c timerfd_ctx.c signalfd.c signalfd_ctx.c eventfd.c - eventfd_ctx.c - common.c) + eventfd_ctx.c) target_link_libraries(epoll-shim PRIVATE Threads::Threads rt) target_include_directories( epoll-shim diff --git a/src/common.c b/src/common.c deleted file mode 100644 index 0c7da1c..0000000 --- a/src/common.c +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include -#include -#include - -struct signalfd_context; -struct timerfd_context; -struct eventfd_context; - -extern pthread_mutex_t timerfd_context_mtx; -extern struct timerfd_context *get_timerfd_context(int fd, bool create_new); -extern ssize_t timerfd_read(struct timerfd_context *, void *buf, - size_t nbytes); -extern int timerfd_close(struct timerfd_context *); - -extern pthread_mutex_t signalfd_context_mtx; -extern struct signalfd_context *get_signalfd_context(int fd, bool create_new); -extern ssize_t signalfd_read(struct signalfd_context *, void *buf, - size_t nbytes); -extern int signalfd_close(struct signalfd_context *); - -extern pthread_mutex_t eventfd_context_mtx; -extern struct eventfd_context *get_eventfd_context(int fd, bool create_new); -extern ssize_t eventfd_helper_read(struct eventfd_context *, void *buf, - size_t nbytes); -extern ssize_t eventfd_helper_write(struct eventfd_context *, void const *buf, - size_t nbytes); -extern int eventfd_close(struct eventfd_context *); - -#define WRAP(context, return_type, call, unlock_after_call) \ - if (fd >= 0) { \ - pthread_mutex_lock(&context##_mtx); \ - struct context *ctx = get_##context(fd, false); \ - if (ctx) { \ - return_type ret = (call); \ - if (unlock_after_call) { \ - pthread_mutex_unlock(&context##_mtx); \ - } \ - return ret; \ - } \ - pthread_mutex_unlock(&context##_mtx); \ - } - -int -epoll_shim_close(int fd) -{ - WRAP(timerfd_context, int, timerfd_close(ctx), true) - WRAP(signalfd_context, int, signalfd_close(ctx), true) - WRAP(eventfd_context, int, eventfd_close(ctx), true) - - return close(fd); -} - -ssize_t -epoll_shim_read(int fd, void *buf, size_t nbytes) -{ - WRAP(timerfd_context, ssize_t, timerfd_read(ctx, buf, nbytes), false) - WRAP(signalfd_context, ssize_t, signalfd_read(ctx, buf, nbytes), false) - WRAP(eventfd_context, ssize_t, eventfd_helper_read(ctx, buf, nbytes), - false) - - return read(fd, buf, nbytes); -} - -ssize_t -epoll_shim_write(int fd, void const *buf, size_t nbytes) -{ - WRAP(eventfd_context, ssize_t, eventfd_helper_write(ctx, buf, nbytes), - false) - - return write(fd, buf, nbytes); -} diff --git a/src/epoll_shim_ctx.c b/src/epoll_shim_ctx.c new file mode 100644 index 0000000..db68dfe --- /dev/null +++ b/src/epoll_shim_ctx.c @@ -0,0 +1,254 @@ +#include "epoll_shim_ctx.h" + +#include + +#include +#include +#include + +static void +fd_context_map_node_init(FDContextMapNode *node, int kq) +{ + node->fd = kq; + node->vtable = NULL; +} + +static FDContextMapNode * +fd_context_map_node_create(int kq, errno_t *ec) +{ + FDContextMapNode *node; + + node = malloc(sizeof(FDContextMapNode)); + if (!node) { + *ec = errno; + return NULL; + } + + fd_context_map_node_init(node, kq); + return node; +} + +static errno_t +fd_context_map_node_terminate(FDContextMapNode *node) +{ + errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0; + + if (close(node->fd) < 0) { + ec = ec ? ec : errno; + } + + return ec; +} + +errno_t +fd_context_map_node_destroy(FDContextMapNode *node) +{ + errno_t ec = fd_context_map_node_terminate(node); + free(node); + return ec; +} + +/**/ + +errno_t +fd_context_default_write(FDContextMapNode *node, /**/ + const void *buf, size_t nbytes, size_t *bytes_transferred) +{ + /* TODO(jan): Verify this. */ + return EBADF; +} + +/**/ + +static int +fd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2) +{ + return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); +} + +RB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry, + fd_context_map_node_cmp) + +EpollShimCtx epoll_shim_ctx = { + .fd_context_map = RB_INITIALIZER(&fd_context_map), + .mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +static FDContextMapNode * +epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq, + errno_t *ec) +{ + FDContextMapNode *node; + + { + FDContextMapNode find; + find.fd = kq; + + node = RB_FIND(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, &find); + } + + if (node) { + (void)fd_context_map_node_terminate(node); + fd_context_map_node_init(node, kq); + } else { + node = fd_context_map_node_create(kq, ec); + if (!node) { + return NULL; + } + + if (RB_INSERT(fd_context_map_, &epoll_shim_ctx->fd_context_map, + node)) { + assert(0); + } + } + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec) +{ + FDContextMapNode *node; + + int kq = kqueue(); + if (kq < 0) { + *ec = errno; + return NULL; + } + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + if (!node) { + close(kq); + } + + return node; +} + +static FDContextMapNode * +epoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + FDContextMapNode find; + find.fd = fd; + + node = RB_FIND(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, &find); + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); + if (node) { + RB_REMOVE(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, node); + } + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + return node; +} + +void +epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, + FDContextMapNode *node) +{ + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + RB_REMOVE(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, node); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); +} + +/**/ + +int +epoll_shim_close(int fd) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd); + if (!node) { + return close(fd); + } + + errno_t ec = fd_context_map_node_destroy(node); + if (ec != 0) { + errno = ec; + return -1; + } + + return 0; +} + +ssize_t +epoll_shim_read(int fd, void *buf, size_t nbytes) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node) { + return read(fd, buf, nbytes); + } + + if (nbytes > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + size_t bytes_transferred; + errno_t ec = node->vtable->read_fun(node, /**/ + buf, nbytes, &bytes_transferred); + if (ec != 0) { + errno = ec; + return -1; + } + + return (ssize_t)bytes_transferred; +} + +ssize_t +epoll_shim_write(int fd, void const *buf, size_t nbytes) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node) { + return write(fd, buf, nbytes); + } + + if (nbytes > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + size_t bytes_transferred; + errno_t ec = node->vtable->write_fun(node, /**/ + buf, nbytes, &bytes_transferred); + if (ec != 0) { + errno = ec; + return -1; + } + + return (ssize_t)bytes_transferred; +} diff --git a/src/epoll_shim_ctx.h b/src/epoll_shim_ctx.h new file mode 100644 index 0000000..959090b --- /dev/null +++ b/src/epoll_shim_ctx.h @@ -0,0 +1,70 @@ +#ifndef EPOLL_SHIM_CTX_H_ +#define EPOLL_SHIM_CTX_H_ + +#include + +#include + +#include "eventfd_ctx.h" +#include "signalfd_ctx.h" +#include "timerfd_ctx.h" + +struct fd_context_map_node_; +typedef struct fd_context_map_node_ FDContextMapNode; + +typedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred); +typedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/ + const void *buf, size_t nbytes, size_t *bytes_transferred); +typedef errno_t (*fd_context_close_fun)(FDContextMapNode *node); + +typedef struct { + fd_context_read_fun read_fun; + fd_context_write_fun write_fun; + fd_context_close_fun close_fun; +} FDContextVTable; + +errno_t fd_context_default_write(FDContextMapNode *node, /**/ + const void *buf, size_t nbytes, size_t *bytes_transferred); + +struct fd_context_map_node_ { + RB_ENTRY(fd_context_map_node_) entry; + int fd; + int flags; + union { + EventFDCtx eventfd; + TimerFDCtx timerfd; + SignalFDCtx signalfd; + } ctx; + FDContextVTable const *vtable; +}; + +errno_t fd_context_map_node_destroy(FDContextMapNode *node); + +/**/ + +typedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap; + +typedef struct { + FDContextMap fd_context_map; + pthread_mutex_t mutex; +} EpollShimCtx; + +extern EpollShimCtx epoll_shim_ctx; + +FDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, + errno_t *ec); +FDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, + int fd); +FDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, + int fd); +void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, + FDContextMapNode *node); + +/**/ + +int epoll_shim_close(int fd); +ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes); +ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes); + +#endif diff --git a/src/eventfd.c b/src/eventfd.c index 2988c8c..31d0e3d 100644 --- a/src/eventfd.c +++ b/src/eventfd.c @@ -17,92 +17,7 @@ #include #include -#include "eventfd_ctx.h" - -struct eventfd_context { - int fd; - int flags; - EventFDCtx ctx; - struct eventfd_context *next; -}; - -static struct eventfd_context *eventfd_contexts; -pthread_mutex_t eventfd_context_mtx = PTHREAD_MUTEX_INITIALIZER; - -struct eventfd_context * -get_eventfd_context(int fd, bool create_new) -{ - for (struct eventfd_context *ctx = eventfd_contexts; ctx; - ctx = ctx->next) { - if (fd == ctx->fd) { - return ctx; - } - } - - if (create_new) { - struct eventfd_context *new_ctx = - malloc(sizeof(struct eventfd_context)); - if (!new_ctx) { - return NULL; - } - new_ctx->fd = -1; - new_ctx->next = eventfd_contexts; - eventfd_contexts = new_ctx; - return new_ctx; - } - - return NULL; -} - -static int -eventfd_impl(unsigned int initval, int flags) -{ - if (flags & ~(EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK)) { - errno = EINVAL; - return -1; - } - - /* - * Don't check that EFD_CLOEXEC is set -- but our kqueue based eventfd - * will always be CLOEXEC. - */ - - struct eventfd_context *ctx = get_eventfd_context(-1, true); - if (!ctx) { - errno = EMFILE; - return -1; - } - - ctx->fd = kqueue(); - if (ctx->fd < 0) { - return -1; - } - - int ctx_flags = 0; - if (flags & EFD_SEMAPHORE) { - ctx_flags |= EVENTFD_CTX_FLAG_SEMAPHORE; - } - - errno_t ec; - if ((ec = eventfd_ctx_init(&ctx->ctx, /**/ - ctx->fd, initval, ctx_flags)) != 0) { - errno = ec; - return -1; - } - - ctx->flags = flags; - - return ctx->fd; -} - -int -eventfd(unsigned int initval, int flags) -{ - pthread_mutex_lock(&eventfd_context_mtx); - int ret = eventfd_impl(initval, flags); - pthread_mutex_unlock(&eventfd_context_mtx); - return ret; -} +#include "epoll_shim_ctx.h" static errno_t eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, @@ -121,89 +36,129 @@ eventfd_ctx_read_or_block(EventFDCtx *eventfd_ctx, uint64_t *value, } } -ssize_t -eventfd_helper_read(struct eventfd_context *ctx, void *buf, size_t nbytes) +static errno_t +eventfd_helper_read(FDContextMapNode *node, void *buf, size_t nbytes, + size_t *bytes_transferred) { - int fd = ctx->fd; - EventFDCtx *efd_ctx = &ctx->ctx; - int flags = ctx->flags; - pthread_mutex_unlock(&eventfd_context_mtx); - if (nbytes != sizeof(uint64_t)) { - errno = EINVAL; - return -1; + return EINVAL; } + uint64_t value; errno_t ec; - if ((ec = eventfd_ctx_read_or_block(efd_ctx, buf, - flags & EFD_NONBLOCK)) != 0) { - errno = ec; - return -1; + if ((ec = eventfd_ctx_read_or_block(&node->ctx.eventfd, &value, + node->flags & EFD_NONBLOCK)) != 0) { + return ec; } - return (ssize_t)nbytes; + memcpy(buf, &value, sizeof(value)); + *bytes_transferred = sizeof(value); + return 0; } -ssize_t -eventfd_helper_write(struct eventfd_context *ctx, void const *buf, - size_t nbytes) +static errno_t +eventfd_helper_write(FDContextMapNode *node, void const *buf, size_t nbytes, + size_t *bytes_transferred) { - int fd = ctx->fd; - EventFDCtx *efd_ctx = &ctx->ctx; - pthread_mutex_unlock(&eventfd_context_mtx); - if (nbytes != sizeof(uint64_t)) { - errno = EINVAL; - return -1; + return EINVAL; } uint64_t value; memcpy(&value, buf, sizeof(uint64_t)); errno_t ec; - if ((ec = eventfd_ctx_write(efd_ctx, value)) != 0) { - errno = ec; - return -1; + if ((ec = eventfd_ctx_write(&node->ctx.eventfd, value)) != 0) { + return ec; } - return (ssize_t)sizeof(uint64_t); + *bytes_transferred = sizeof(value); + return 0; +} + +static errno_t +eventfd_close(FDContextMapNode *node) +{ + return eventfd_ctx_terminate(&node->ctx.eventfd); } -int -eventfd_close(struct eventfd_context *ctx) +static FDContextVTable const eventfd_vtable = { + .read_fun = eventfd_helper_read, + .write_fun = eventfd_helper_write, + .close_fun = eventfd_close, +}; + +static FDContextMapNode * +eventfd_impl(unsigned int initval, int flags, errno_t *ec) { - errno_t ec = eventfd_ctx_terminate(&ctx->ctx); + FDContextMapNode *node; - if (close(ctx->fd) < 0) { - ec = ec ? ec : errno; + if (flags & ~(EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK)) { + *ec = EINVAL; + return NULL; } - ctx->fd = -1; - if (ec) { + /* + * Don't check that EFD_CLOEXEC is set -- but our kqueue based eventfd + * will always be CLOEXEC. + */ + + node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); + if (!node) { + return NULL; + } + + node->flags = flags; + + int ctx_flags = 0; + if (flags & EFD_SEMAPHORE) { + ctx_flags |= EVENTFD_CTX_FLAG_SEMAPHORE; + } + + if ((*ec = eventfd_ctx_init(&node->ctx.eventfd, /**/ + node->fd, initval, ctx_flags)) != 0) { + goto fail; + } + + node->vtable = &eventfd_vtable; + return node; + +fail: + epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); + (void)fd_context_map_node_destroy(node); + return NULL; +} + +int +eventfd(unsigned int initval, int flags) +{ + FDContextMapNode *node; + errno_t ec; + + node = eventfd_impl(initval, flags, &ec); + if (!node) { errno = ec; return -1; } - return 0; + return node->fd; } int eventfd_read(int fd, eventfd_t *value) { - struct eventfd_context *ctx; + FDContextMapNode *node; - (void)pthread_mutex_lock(&eventfd_context_mtx); - ctx = get_eventfd_context(fd, false); - (void)pthread_mutex_unlock(&eventfd_context_mtx); - - if (!ctx) { + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &eventfd_vtable) { errno = EBADF; return -1; } + size_t bytes_transferred; errno_t ec; - if ((ec = eventfd_ctx_read_or_block(&ctx->ctx, value, - ctx->flags & EFD_NONBLOCK)) != 0) { + if ((ec = eventfd_helper_read(node, value, sizeof(*value), + &bytes_transferred)) != 0) { errno = ec; return -1; } @@ -214,19 +169,18 @@ eventfd_read(int fd, eventfd_t *value) int eventfd_write(int fd, eventfd_t value) { - struct eventfd_context *ctx; - - (void)pthread_mutex_lock(&eventfd_context_mtx); - ctx = get_eventfd_context(fd, false); - (void)pthread_mutex_unlock(&eventfd_context_mtx); + FDContextMapNode *node; - if (!ctx) { + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &eventfd_vtable) { errno = EBADF; return -1; } + size_t bytes_transferred; errno_t ec; - if ((ec = eventfd_ctx_write(&ctx->ctx, value)) != 0) { + if ((ec = eventfd_helper_write(node, &value, sizeof(value), + &bytes_transferred)) != 0) { errno = ec; return -1; } diff --git a/src/signalfd.c b/src/signalfd.c index c6a6a93..597f915 100644 --- a/src/signalfd.c +++ b/src/signalfd.c @@ -16,89 +16,7 @@ #include #include -#include "signalfd_ctx.h" - -struct signalfd_context { - int fd; - int flags; - SignalFDCtx ctx; - struct signalfd_context *next; -}; - -static struct signalfd_context *signalfd_contexts; -pthread_mutex_t signalfd_context_mtx = PTHREAD_MUTEX_INITIALIZER; - -struct signalfd_context * -get_signalfd_context(int fd, bool create_new) -{ - for (struct signalfd_context *ctx = signalfd_contexts; ctx; - ctx = ctx->next) { - if (fd == ctx->fd) { - return ctx; - } - } - - if (create_new) { - struct signalfd_context *new_ctx = - calloc(1, sizeof(struct signalfd_context)); - if (!new_ctx) { - return NULL; - } - new_ctx->fd = -1; - new_ctx->next = signalfd_contexts; - signalfd_contexts = new_ctx; - return new_ctx; - } - - return NULL; -} - -static int -signalfd_impl(int fd, const sigset_t *sigs, int flags) -{ - if (fd != -1) { - errno = EINVAL; - return -1; - } - - if (flags & ~(SFD_NONBLOCK | SFD_CLOEXEC)) { - errno = EINVAL; - return -1; - } - - struct signalfd_context *ctx = get_signalfd_context(-1, true); - if (!ctx) { - errno = EMFILE; - return -1; - } - - ctx->fd = kqueue(); - if (ctx->fd < 0) { - return -1; - } - - ctx->flags = flags; - - errno_t err; - - if ((err = signalfd_ctx_init(&ctx->ctx, ctx->fd, sigs)) != 0) { - (void)close(ctx->fd); - ctx->fd = -1; - errno = err; - return -1; - } - - return ctx->fd; -} - -int -signalfd(int fd, const sigset_t *sigs, int flags) -{ - pthread_mutex_lock(&signalfd_context_mtx); - int ret = signalfd_impl(fd, sigs, flags); - pthread_mutex_unlock(&signalfd_context_mtx); - return ret; -} +#include "epoll_shim_ctx.h" static errno_t signalfd_ctx_read_or_block(SignalFDCtx *signalfd_ctx, uint32_t *value, @@ -117,47 +35,88 @@ signalfd_ctx_read_or_block(SignalFDCtx *signalfd_ctx, uint32_t *value, } } -ssize_t -signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes) +static errno_t +signalfd_read(FDContextMapNode *node, void *buf, size_t nbytes, + size_t *bytes_transferred) { - int fd = ctx->fd; - int flags = ctx->flags; - pthread_mutex_unlock(&signalfd_context_mtx); - // TODO(jan): fix this to read multiple signals if (nbytes != sizeof(struct signalfd_siginfo)) { - errno = EINVAL; - return -1; + return EINVAL; } uint32_t signo; - errno_t err; - - if ((err = signalfd_ctx_read_or_block(&ctx->ctx, &signo, - flags & SFD_NONBLOCK)) != 0) { - errno = err; - return -1; + errno_t ec; + if ((ec = signalfd_ctx_read_or_block(&node->ctx.signalfd, &signo, + node->flags & SFD_NONBLOCK)) != 0) { + return ec; } struct signalfd_siginfo siginfo = {.ssi_signo = signo}; memcpy(buf, &siginfo, sizeof(siginfo)); - return (ssize_t)sizeof(siginfo); + + *bytes_transferred = sizeof(siginfo); + return 0; } -int -signalfd_close(struct signalfd_context *ctx) +static errno_t +signalfd_close(FDContextMapNode *node) +{ + return signalfd_ctx_terminate(&node->ctx.signalfd); +} + +static FDContextVTable const signalfd_vtable = { + .read_fun = signalfd_read, + .write_fun = fd_context_default_write, + .close_fun = signalfd_close, +}; + +static FDContextMapNode * +signalfd_impl(int fd, const sigset_t *sigs, int flags, errno_t *ec) { - errno_t ec = signalfd_ctx_terminate(&ctx->ctx); + FDContextMapNode *node; - if (close(ctx->fd) < 0) { - ec = ec ? ec : errno; + if (fd != -1) { + *ec = EINVAL; + return NULL; + } + + if (flags & ~(SFD_NONBLOCK | SFD_CLOEXEC)) { + *ec = EINVAL; + return NULL; + } + + node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); + if (!node) { + return NULL; + } + + node->flags = flags; + + if ((*ec = signalfd_ctx_init(&node->ctx.signalfd, /**/ + node->fd, sigs)) != 0) { + goto fail; } - ctx->fd = -1; - if (ec) { + node->vtable = &signalfd_vtable; + return node; + +fail: + epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); + (void)fd_context_map_node_destroy(node); + return NULL; +} + +int +signalfd(int fd, const sigset_t *sigs, int flags) +{ + FDContextMapNode *node; + errno_t ec; + + node = signalfd_impl(fd, sigs, flags, &ec); + if (!node) { errno = ec; return -1; } - return 0; + return node->fd; } diff --git a/src/timerfd.c b/src/timerfd.c index 1b305ba..1a51d74 100644 --- a/src/timerfd.c +++ b/src/timerfd.c @@ -16,117 +16,121 @@ #include #include -#include "timerfd_ctx.h" +#include "epoll_shim_ctx.h" -struct timerfd_context { - int fd; - int flags; - TimerFDCtx ctx; - struct timerfd_context *next; -}; - -static struct timerfd_context *timerfd_contexts; -pthread_mutex_t timerfd_context_mtx = PTHREAD_MUTEX_INITIALIZER; - -struct timerfd_context * -get_timerfd_context(int fd, bool create_new) +static errno_t +timerfd_ctx_read_or_block(TimerFDCtx *timerfd, uint64_t *value, bool nonblock) { - for (struct timerfd_context *ctx = timerfd_contexts; ctx; - ctx = ctx->next) { - if (fd == ctx->fd) { - return ctx; + for (;;) { + errno_t ec = timerfd_ctx_read(timerfd, value); + if (nonblock || ec != EAGAIN) { + return (ec); } - } - if (create_new) { - struct timerfd_context *new_ctx = - malloc(sizeof(struct timerfd_context)); - if (!new_ctx) { - return NULL; + struct pollfd pfd = {.fd = timerfd->kq, .events = POLLIN}; + if (poll(&pfd, 1, -1) < 0) { + return (errno); } + } +} - *new_ctx = (struct timerfd_context){ - .fd = -1, - .next = timerfd_contexts, - }; - timerfd_contexts = new_ctx; +static errno_t +timerfd_read(FDContextMapNode *node, void *buf, size_t nbytes, + size_t *bytes_transferred) +{ + if (nbytes < sizeof(uint64_t)) { + return EINVAL; + } - return new_ctx; + errno_t ec; + uint64_t nr_expired; + if ((ec = timerfd_ctx_read_or_block(&node->ctx.timerfd, &nr_expired, + node->flags & TFD_NONBLOCK)) != 0) { + return ec; } - return NULL; + memcpy(buf, &nr_expired, sizeof(uint64_t)); + + *bytes_transferred = sizeof(uint64_t); + return 0; } static errno_t -timerfd_create_impl(int clockid, int flags, int *fd) +timerfd_close(FDContextMapNode *node) { - errno_t err; + return timerfd_ctx_terminate(&node->ctx.timerfd); +} + +static FDContextVTable const timerfd_vtable = { + .read_fun = timerfd_read, + .write_fun = fd_context_default_write, + .close_fun = timerfd_close, +}; + +static FDContextMapNode * +timerfd_create_impl(int clockid, int flags, errno_t *ec) +{ + FDContextMapNode *node; if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) { - return EINVAL; + *ec = EINVAL; + return NULL; } if (flags & ~(TFD_CLOEXEC | TFD_NONBLOCK)) { - return EINVAL; + *ec = EINVAL; + return NULL; } - struct timerfd_context *ctx = get_timerfd_context(-1, true); - if (!ctx) { - return ENOMEM; + node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); + if (!node) { + return NULL; } - ctx->fd = kqueue(); - if (ctx->fd < 0) { - assert(errno != 0); - return errno; - } + node->flags = flags; - if ((err = timerfd_ctx_init(&ctx->ctx, ctx->fd, clockid)) != 0) { - goto out; + if ((*ec = timerfd_ctx_init(&node->ctx.timerfd, /**/ + node->fd, clockid)) != 0) { + goto fail; } - ctx->flags = flags; - - *fd = ctx->fd; - return 0; + node->vtable = &timerfd_vtable; + return node; -out: - close(ctx->fd); - ctx->fd = -1; - return err; +fail: + epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); + (void)fd_context_map_node_destroy(node); + return NULL; } int timerfd_create(int clockid, int flags) { - int fd; - errno_t err; - - pthread_mutex_lock(&timerfd_context_mtx); - err = timerfd_create_impl(clockid, flags, &fd); - pthread_mutex_unlock(&timerfd_context_mtx); + FDContextMapNode *node; + errno_t ec; - if (err != 0) { - errno = err; + node = timerfd_create_impl(clockid, flags, &ec); + if (!node) { + errno = ec; return -1; } - return fd; + return node->fd; } static errno_t timerfd_settime_impl(int fd, int flags, const struct itimerspec *new, struct itimerspec *old) { - errno_t err; - struct timerfd_context *ctx; + errno_t ec; + FDContextMapNode *node; if (!new) { return EFAULT; } - ctx = get_timerfd_context(fd, false); - if (!ctx) { + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node) { return EINVAL; } @@ -134,10 +138,10 @@ timerfd_settime_impl(int fd, int flags, const struct itimerspec *new, return EINVAL; } - if ((err = timerfd_ctx_settime(&ctx->ctx, + if ((ec = timerfd_ctx_settime(&node->ctx.timerfd, (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, /**/ new, old)) != 0) { - return err; + return ec; } return 0; @@ -147,14 +151,9 @@ int timerfd_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old) { - errno_t err; - - pthread_mutex_lock(&timerfd_context_mtx); - err = timerfd_settime_impl(fd, flags, new, old); - pthread_mutex_unlock(&timerfd_context_mtx); - - if (err != 0) { - errno = err; + errno_t ec = timerfd_settime_impl(fd, flags, new, old); + if (ec != 0) { + errno = ec; return -1; } @@ -167,60 +166,3 @@ int timerfd_gettime(int fd, struct itimerspec *cur) return syscall(SYS_timerfd_gettime, fd, cur); } #endif - -static errno_t -timerfd_ctx_read_or_block(TimerFDCtx *timerfd, uint64_t *value, bool nonblock) -{ - for (;;) { - errno_t ec = timerfd_ctx_read(timerfd, value); - if (nonblock || ec != EAGAIN) { - return (ec); - } - - struct pollfd pfd = {.fd = timerfd->kq, .events = POLLIN}; - if (poll(&pfd, 1, -1) < 0) { - return (errno); - } - } -} - -ssize_t -timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes) -{ - pthread_mutex_unlock(&timerfd_context_mtx); - - if (nbytes < sizeof(uint64_t)) { - errno = EINVAL; - return -1; - } - - errno_t err; - uint64_t nr_expired; - if ((err = timerfd_ctx_read_or_block(&ctx->ctx, &nr_expired, - ctx->flags & TFD_NONBLOCK)) != 0) { - errno = err; - return -1; - } - - memcpy(buf, &nr_expired, sizeof(uint64_t)); - - return sizeof(uint64_t); -} - -int -timerfd_close(struct timerfd_context *ctx) -{ - errno_t ec = timerfd_ctx_terminate(&ctx->ctx); - - if (close(ctx->fd) < 0) { - ec = ec ? ec : errno; - } - ctx->fd = -1; - - if (ec) { - errno = ec; - return -1; - } - - return 0; -} From aa62cc41dac8d0ae6bd49bd39aa61e2eff076bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 15:31:56 +0100 Subject: [PATCH 47/75] check for correct type in timerfd_settime_impl --- src/timerfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timerfd.c b/src/timerfd.c index 1a51d74..4148ca2 100644 --- a/src/timerfd.c +++ b/src/timerfd.c @@ -130,7 +130,7 @@ timerfd_settime_impl(int fd, int flags, const struct itimerspec *new, } node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); - if (!node) { + if (!node || node->vtable != &timerfd_vtable) { return EINVAL; } From 078493a5e29ee024096979089ff2e77819d92d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 15:45:20 +0100 Subject: [PATCH 48/75] fix compiler errors under FreeBSD 11.3 --- src/eventfd_ctx.c | 2 ++ src/signalfd_ctx.c | 2 ++ src/timerfd_ctx.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index eeb94a2..67e6459 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -1,5 +1,7 @@ #include "eventfd_ctx.h" +#include + #include #include diff --git a/src/signalfd_ctx.c b/src/signalfd_ctx.c index b92e6f5..96958db 100644 --- a/src/signalfd_ctx.c +++ b/src/signalfd_ctx.c @@ -1,5 +1,7 @@ #include "signalfd_ctx.h" +#include + #include #include diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index 4575050..280605c 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -1,5 +1,7 @@ #include "timerfd_ctx.h" +#include + #include #include From 301e8124167f056d6ade6c3ea706cf614f351a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 15:56:06 +0100 Subject: [PATCH 49/75] add worst case performance test of old lookup mechanism --- test/CMakeLists.txt | 4 ++++ test/perf-many-fds.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 test/perf-many-fds.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5d5951c..d51a4d3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,3 +34,7 @@ target_link_libraries(eventfd-ctx-test PRIVATE Threads::Threads) target_include_directories(eventfd-ctx-test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../src") add_test(NAME eventfd-ctx-test COMMAND eventfd-ctx-test) + +add_executable(perf-many-fds perf-many-fds.c) +target_link_libraries(perf-many-fds PRIVATE epoll-shim::epoll-shim) +add_test(NAME perf-many-fds COMMAND perf-many-fds) diff --git a/test/perf-many-fds.c b/test/perf-many-fds.c new file mode 100644 index 0000000..ec949d5 --- /dev/null +++ b/test/perf-many-fds.c @@ -0,0 +1,34 @@ +#include + +#include +#include + +#define REQUIRE(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "REQUIRE in line %d failed.\n", \ + __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +#define NR_EVENTFDS (50000) + +int +main() +{ + int *eventfds = malloc(NR_EVENTFDS * sizeof(int)); + REQUIRE(eventfds); + + for (long i = 0; i < NR_EVENTFDS; ++i) { + eventfds[i] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + REQUIRE(eventfds[i] >= 0); + } + + for (long i = 0; i < 1000000; ++i) { + REQUIRE(eventfd_write(eventfds[0], 1) == 0); + if (i % 10000 == 0) { + fprintf(stderr, "."); + } + } +} From e8c44357cb60d4b4e509c2fe43b3e9d9bd8a4e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 15:59:12 +0100 Subject: [PATCH 50/75] require that perf-many-fds test does not take longer than 10s --- test/perf-many-fds.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/perf-many-fds.c b/test/perf-many-fds.c index ec949d5..4139ba4 100644 --- a/test/perf-many-fds.c +++ b/test/perf-many-fds.c @@ -2,6 +2,7 @@ #include #include +#include #define REQUIRE(x) \ do { \ @@ -17,6 +18,11 @@ int main() { + struct timespec time1; + struct timespec time2; + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &time1) == 0); + int *eventfds = malloc(NR_EVENTFDS * sizeof(int)); REQUIRE(eventfds); @@ -31,4 +37,7 @@ main() fprintf(stderr, "."); } } + + REQUIRE(clock_gettime(CLOCK_MONOTONIC, &time2) == 0); + REQUIRE(time2.tv_sec - time1.tv_sec < 10); } From b544984febceee005d9801a495cd5cfb33ad280e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 16:01:32 +0100 Subject: [PATCH 51/75] perf-many-fds: reduce number of fds a bit --- test/perf-many-fds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/perf-many-fds.c b/test/perf-many-fds.c index 4139ba4..cf6eb27 100644 --- a/test/perf-many-fds.c +++ b/test/perf-many-fds.c @@ -13,7 +13,7 @@ } \ } while (0) -#define NR_EVENTFDS (50000) +#define NR_EVENTFDS (20000) int main() @@ -31,7 +31,7 @@ main() REQUIRE(eventfds[i] >= 0); } - for (long i = 0; i < 1000000; ++i) { + for (long i = 0; i < 2000000; ++i) { REQUIRE(eventfd_write(eventfds[0], 1) == 0); if (i % 10000 == 0) { fprintf(stderr, "."); From c6b2829dc7d968e4ad1bcc334231e9cd25e65a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 16:03:11 +0100 Subject: [PATCH 52/75] perf-many-fds: increase timeout to 15s --- test/perf-many-fds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/perf-many-fds.c b/test/perf-many-fds.c index cf6eb27..9698992 100644 --- a/test/perf-many-fds.c +++ b/test/perf-many-fds.c @@ -39,5 +39,5 @@ main() } REQUIRE(clock_gettime(CLOCK_MONOTONIC, &time2) == 0); - REQUIRE(time2.tv_sec - time1.tv_sec < 10); + REQUIRE(time2.tv_sec - time1.tv_sec < 15); } From 7fd52301d5fb551f669ab42413d193fa9fb5ec88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 17:26:53 +0100 Subject: [PATCH 53/75] fix compiler warnings --- CMakeLists.txt | 18 +++++++++++++++++- src/epoll.c | 32 +++++++++++++++++++------------- src/epoll_shim_ctx.c | 4 ++++ src/eventfd_ctx.c | 1 + src/signalfd_ctx.c | 2 +- src/timerfd_ctx.c | 8 +++++--- 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b632ea..73a3d77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,23 @@ cmake_minimum_required(VERSION 3.14) project(epoll-shim LANGUAGES C) option(BUILD_SHARED_LIBS "build libepoll-shim as shared lib" ON) +option(ENABLE_COMPILER_WARNINGS "enable compiler warnings" OFF) + +if(ENABLE_COMPILER_WARNINGS) + add_compile_options("-Wall" + "-Wextra" + "-Wconversion" + "-Wsign-conversion" + "-Wmissing-prototypes" + "-pedantic" + "-Werror=implicit-function-declaration" + "-Werror=return-type" + "-Werror=incompatible-pointer-types") +endif() include(CTest) -set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD 11) set(CMAKE_C_EXTENSIONS ON) add_subdirectory(src) @@ -17,6 +30,9 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) file(WRITE "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" "add_library(${_namespace}::epoll-shim ALIAS epoll-shim)\n") set(${PROJECT_NAME}_DIR "${PROJECT_BINARY_DIR}") + if(ENABLE_COMPILER_WARNINGS) + add_compile_options("-w") + endif() add_subdirectory(test) endif() diff --git a/src/epoll.c b/src/epoll.c index b4b47c7..4c6ae95 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -164,7 +164,8 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) if ((!ev && op != EPOLL_CTL_DEL) || (ev && ((ev->events & - ~(EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP | EPOLLERR)) + ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLHUP /**/ + | EPOLLRDHUP | EPOLLERR)) /* the user should really set one of EPOLLIN or EPOLLOUT * so that EPOLLHUP and EPOLLERR work. Don't make this a * hard error for now, though. */ @@ -178,7 +179,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) return (-1); } - if ((e = kqueue_load_state(fd, fd2, &flags)) < 0) { + if ((e = kqueue_load_state(fd, (uint32_t)fd2, &flags)) < 0) { errno = -e; return (-1); } @@ -303,9 +304,9 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) if (kev[i].data != 0) { if (i == 0 && (kev[i].data == ENOENT || kev[i].data == EBADF)) { - kqueue_save_state(fd, fd2, 0); + kqueue_save_state(fd, (uint32_t)fd2, 0); } - errno = kev[i].data; + errno = (int)kev[i].data; return (-1); } } @@ -326,7 +327,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) /* If the fstat fails for some reason we must clear * internal state to avoid EEXIST errors in future * calls to epoll_ctl. */ - (void)kqueue_save_state(fd, fd2, 0); + (void)kqueue_save_state(fd, (uint32_t)fd2, 0); return (-1); } @@ -337,7 +338,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) } } - if ((e = kqueue_save_state(fd, fd2, flags)) < 0) { + if ((e = kqueue_save_state(fd, (uint32_t)fd2, flags)) < 0) { errno = -e; return (-1); } @@ -414,11 +415,12 @@ again:; events |= EPOLLIN; if (evlist[i].flags & EV_ONESHOT) { uint16_t flags = 0; - kqueue_load_state(fd, evlist[i].ident, &flags); + kqueue_load_state(fd, + (uint32_t)evlist[i].ident, &flags); if (flags & KQUEUE_STATE_NYCSS) { if (is_not_yet_connected_stream_socket( - evlist[i].ident)) { + (int)evlist[i].ident)) { events = EPOLLHUP; if (flags & @@ -459,7 +461,8 @@ again:; kevent(fd, nkev, 2, NULL, 0, NULL); kqueue_save_state(fd, - evlist[i].ident, flags); + (uint32_t)evlist[i].ident, + flags); continue; } @@ -479,7 +482,8 @@ again:; } uint16_t flags = 0; - kqueue_load_state(fd, evlist[i].ident, &flags); + kqueue_load_state(fd, (uint32_t)evlist[i].ident, + &flags); int epoll_event; @@ -516,8 +520,10 @@ again:; return -1; } - struct pollfd pfd = {.fd = evlist[i].ident, - .events = POLLIN | POLLOUT | POLLHUP}; + struct pollfd pfd = { + .fd = (int)evlist[i].ident, + .events = POLLIN | POLLOUT | POLLHUP, + }; if (poll(&pfd, 1, 0) == 1) { if (pfd.revents & POLLHUP) { @@ -563,7 +569,7 @@ again:; events |= epoll_event; } - ev[j].events = events; + ev[j].events = (uint32_t)events; ev[j].data.ptr = evlist[i].udata; ++j; } diff --git a/src/epoll_shim_ctx.c b/src/epoll_shim_ctx.c index db68dfe..6463294 100644 --- a/src/epoll_shim_ctx.c +++ b/src/epoll_shim_ctx.c @@ -54,6 +54,10 @@ errno_t fd_context_default_write(FDContextMapNode *node, /**/ const void *buf, size_t nbytes, size_t *bytes_transferred) { + (void)node; + (void)buf; + (void)nbytes; + (void)bytes_transferred; /* TODO(jan): Verify this. */ return EBADF; } diff --git a/src/eventfd_ctx.c b/src/eventfd_ctx.c index 67e6459..52a1057 100644 --- a/src/eventfd_ctx.c +++ b/src/eventfd_ctx.c @@ -49,6 +49,7 @@ eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags) errno_t eventfd_ctx_terminate(EventFDCtx *eventfd) { + (void)eventfd; return (0); } diff --git a/src/signalfd_ctx.c b/src/signalfd_ctx.c index 96958db..15a5485 100644 --- a/src/signalfd_ctx.c +++ b/src/signalfd_ctx.c @@ -54,6 +54,6 @@ signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident) return EAGAIN; } - *ident = kev.ident; + *ident = (uint32_t)kev.ident; return 0; } diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index 280605c..6f8372b 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -38,7 +38,8 @@ worker_function(void *arg) if (sigwaitinfo(&rt_set, &info) != SIGRTMIN) { break; } - total_expirations += 1 + timer_getoverrun(ctx->complx.timer); + int overrun = timer_getoverrun(ctx->complx.timer); + total_expirations += 1 + (uint64_t)MAX(0, overrun); EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, (void *)(uintptr_t)total_expirations); (void)kevent(ctx->kq, &kev, 1, NULL, 0, NULL); @@ -275,9 +276,10 @@ timerfd_ctx_read_impl(TimerFDCtx *timerfd, uint64_t *value) expired_new; } } else { - assert(!kev.udata && kev.filter == EVFILT_TIMER); + assert(!kev.udata && kev.filter == EVFILT_TIMER && + kev.data >= 0); - nr_expired = kev.data; + nr_expired = (uint64_t)kev.data; } if (nr_expired != 0) { From ffe71b98a20513ac9a0db3b69b4efc7b4cc16030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 18:05:14 +0100 Subject: [PATCH 54/75] port many-timers test to Linux --- test/CMakeLists.txt | 35 ++++++++++++++++++++++------------- test/many-timers.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d51a4d3..3bc61a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,14 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.10) project(epoll-shim-tests LANGUAGES C) -find_package(epoll-shim REQUIRED) +include(CTest) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(epoll-shim REQUIRED) +else() + add_library(epoll-shim INTERFACE) + add_library(epoll-shim::epoll-shim ALIAS epoll-shim) +endif() set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -15,11 +22,6 @@ add_executable(expire-five expire-five.c) target_link_libraries(expire-five PRIVATE epoll-shim::epoll-shim) add_test(NAME expire-five COMMAND expire-five) -add_executable(kqueue-state kqueue-state.c) -target_include_directories(kqueue-state - PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../include") -add_test(NAME kqueue-state COMMAND kqueue-state) - add_executable(many-timers many-timers.c) target_link_libraries(many-timers PRIVATE epoll-shim::epoll-shim) add_test(NAME many-timers COMMAND many-timers) @@ -29,12 +31,19 @@ target_link_libraries(tst-eventfd PRIVATE epoll-shim::epoll-shim Threads::Threads) add_test(NAME tst-eventfd COMMAND tst-eventfd) -add_executable(eventfd-ctx-test eventfd-ctx-test.c) -target_link_libraries(eventfd-ctx-test PRIVATE Threads::Threads) -target_include_directories(eventfd-ctx-test - PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../src") -add_test(NAME eventfd-ctx-test COMMAND eventfd-ctx-test) - add_executable(perf-many-fds perf-many-fds.c) target_link_libraries(perf-many-fds PRIVATE epoll-shim::epoll-shim) add_test(NAME perf-many-fds COMMAND perf-many-fds) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_executable(kqueue-state kqueue-state.c) + target_include_directories(kqueue-state + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../include") + add_test(NAME kqueue-state COMMAND kqueue-state) + + add_executable(eventfd-ctx-test eventfd-ctx-test.c) + target_link_libraries(eventfd-ctx-test PRIVATE Threads::Threads) + target_include_directories(eventfd-ctx-test + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../src") + add_test(NAME eventfd-ctx-test COMMAND eventfd-ctx-test) +endif() diff --git a/test/many-timers.c b/test/many-timers.c index c101a7a..72359c4 100644 --- a/test/many-timers.c +++ b/test/many-timers.c @@ -1,17 +1,24 @@ #include +#ifndef __linux__ #include +#include +#endif + #include #include #include -#include -#include -#include +#include #include #include +#include #include +#include +#include +#include + #include #define REQUIRE(x) \ @@ -37,6 +44,11 @@ } while (0) #endif +#ifndef nitems +#define nitems(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +#ifndef __linux__ static bool is_fast_timer(int fd) { @@ -47,6 +59,7 @@ is_fast_timer(int fd) close(fd); return (is_fast); } +#endif int main() @@ -82,7 +95,9 @@ main() REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 100000000 && e.tv_nsec < 150000000); +#ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); +#endif } { @@ -117,7 +132,9 @@ main() (ssize_t)sizeof(timeouts)); REQUIRE(timeouts == 2); +#ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); +#endif } { @@ -152,7 +169,9 @@ main() (ssize_t)sizeof(timeouts)); REQUIRE(timeouts == 2); +#ifndef __linux__ REQUIRE(!is_fast_timer(timerfd)); +#endif } { @@ -192,7 +211,9 @@ main() REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 150000000 && e.tv_nsec < 200000000); +#ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); +#endif } { @@ -240,6 +261,8 @@ main() }; REQUIRE(timerfd_settime(timerfd, 0, &time, NULL) == 0); +#ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); +#endif } } From d6c4506adb94067deb51b76b0ddea6434f44a8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 18:27:26 +0100 Subject: [PATCH 55/75] autoformat --- test/many-timers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/many-timers.c b/test/many-timers.c index 72359c4..eba62d8 100644 --- a/test/many-timers.c +++ b/test/many-timers.c @@ -11,8 +11,8 @@ #include #include -#include #include +#include #include #include @@ -45,7 +45,7 @@ #endif #ifndef nitems -#define nitems(x) (sizeof((x)) / sizeof((x)[0])) +#define nitems(x) (sizeof((x)) / sizeof((x)[0])) #endif #ifndef __linux__ From 0d564344e4182606f13c1142145f38716d601f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 18:35:16 +0100 Subject: [PATCH 56/75] add test for correct errno value on invalid write calls --- test/epoll-test.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/epoll-test.c b/test/epoll-test.c index 1a33f99..6d82393 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1563,6 +1563,53 @@ test_same_fd_value() return 0; } +static int +test_invalid_writes() +{ + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + return -1; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + return -1; + } + + char dummy = 0; + if (write(fd, &dummy, 1) >= 0) { + return -1; + } + + if (errno != EINVAL) { + return -1; + } + + close(fd); + + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) { + return -1; + } + + if (write(fd, &dummy, 1) >= 0) { + return -1; + } + + if (errno != EINVAL) { + return -1; + } + + close(fd); + + return 0; +} + int main() { @@ -1617,6 +1664,7 @@ main() TEST(test_recursive_register()); TEST(test_remove_closed()); TEST(test_same_fd_value()); + TEST(test_invalid_writes()); return r; } From 23bb4d7dfb417f0e778102dd4d0c54f400ac133c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 18:45:19 +0100 Subject: [PATCH 57/75] fix 'invalid writes' test --- src/epoll_shim_ctx.c | 4 ++-- test/epoll-test.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/epoll_shim_ctx.c b/src/epoll_shim_ctx.c index 6463294..cdbd9bf 100644 --- a/src/epoll_shim_ctx.c +++ b/src/epoll_shim_ctx.c @@ -58,8 +58,8 @@ fd_context_default_write(FDContextMapNode *node, /**/ (void)buf; (void)nbytes; (void)bytes_transferred; - /* TODO(jan): Verify this. */ - return EBADF; + + return EINVAL; } /**/ diff --git a/test/epoll-test.c b/test/epoll-test.c index 6d82393..ecc5373 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -8,10 +8,12 @@ #include #include +#include #include -#include #include +#include + #include #include From e918e620fc65a88ef90ca152c822d3c6905ff547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 19:09:36 +0100 Subject: [PATCH 58/75] test invalid writes/reads on epoll fd --- test/epoll-test.c | 72 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/test/epoll-test.c b/test/epoll-test.c index ecc5373..9573248 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -1578,36 +1578,66 @@ test_invalid_writes() return -1; } - fd = signalfd(-1, &mask, 0); - if (fd < 0) { - return -1; - } - char dummy = 0; - if (write(fd, &dummy, 1) >= 0) { - return -1; - } - if (errno != EINVAL) { - return -1; - } + { + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + return -1; + } - close(fd); + if (write(fd, &dummy, 1) >= 0) { + return -1; + } - fd = timerfd_create(CLOCK_MONOTONIC, 0); - if (fd < 0) { - return -1; - } + if (errno != EINVAL) { + return -1; + } - if (write(fd, &dummy, 1) >= 0) { - return -1; + close(fd); } - if (errno != EINVAL) { - return -1; + { + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) { + return -1; + } + + if (write(fd, &dummy, 1) >= 0) { + return -1; + } + + if (errno != EINVAL) { + return -1; + } + + close(fd); } - close(fd); + { + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd < 0) { + return -1; + } + + if (write(fd, &dummy, 1) >= 0) { + return -1; + } + + if (errno != EINVAL) { + return -1; + } + + if (read(fd, &dummy, 1) >= 0) { + return -1; + } + + if (errno != EINVAL) { + return -1; + } + + close(fd); + } return 0; } From 71af8c103bb4325abdf9641ad407f15692a860f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 19:42:13 +0100 Subject: [PATCH 59/75] create simple EpollFDCtx, to be expanded later --- include/sys/epoll.h | 3 -- src/CMakeLists.txt | 1 + src/epoll.c | 67 ++++++++++++++++++++++++++++++++++++++++---- src/epoll_shim_ctx.c | 14 ++++++++- src/epoll_shim_ctx.h | 6 +++- src/epollfd_ctx.c | 23 +++++++++++++++ src/epollfd_ctx.h | 13 +++++++++ test/CMakeLists.txt | 2 ++ 8 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 src/epollfd_ctx.c create mode 100644 src/epollfd_ctx.h diff --git a/include/sys/epoll.h b/include/sys/epoll.h index 7ee8574..7d3af4c 100644 --- a/include/sys/epoll.h +++ b/include/sys/epoll.h @@ -67,8 +67,6 @@ int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); #endif -#ifdef notyet -#error don't include this yet #ifndef SHIM_SYS_SHIM_HELPERS #define SHIM_SYS_SHIM_HELPERS #include /* IWYU pragma: keep */ @@ -76,7 +74,6 @@ int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); extern int epoll_shim_close(int); #define close epoll_shim_close #endif -#endif #ifdef __cplusplus diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f5ec2d0..e5c91ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ find_package(Threads REQUIRED) add_library(epoll-shim epoll_shim_ctx.c epoll.c + epollfd_ctx.c timerfd.c timerfd_ctx.c signalfd.c diff --git a/src/epoll.c b/src/epoll.c index 4c6ae95..29b8592 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -9,24 +9,81 @@ #include #include +#include "epoll_shim_ctx.h" + +static errno_t +epollfd_close(FDContextMapNode *node) +{ + return epollfd_ctx_terminate(&node->ctx.epollfd); +} + +static FDContextVTable const epollfd_vtable = { + .read_fun = fd_context_default_read, + .write_fun = fd_context_default_write, + .close_fun = epollfd_close, +}; + +static FDContextMapNode * +epoll_create_impl(errno_t *ec) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); + if (!node) { + return NULL; + } + + node->flags = 0; + + if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/ + node->fd)) != 0) { + goto fail; + } + + node->vtable = &epollfd_vtable; + return node; + +fail: + epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); + (void)fd_context_map_node_destroy(node); + return NULL; +} + +static int +epoll_create_common(void) +{ + FDContextMapNode *node; + errno_t ec; + + node = epoll_create_impl(&ec); + if (!node) { + errno = ec; + return -1; + } + + return node->fd; +} + int epoll_create(int size) { - (void)size; + if (size <= 0) { + errno = EINVAL; + return -1; + } - errno = EINVAL; - return -1; + return epoll_create_common(); } int epoll_create1(int flags) { - if (flags != EPOLL_CLOEXEC) { + if (flags & ~EPOLL_CLOEXEC) { errno = EINVAL; return -1; } - return kqueue(); + return epoll_create_common(); } static int poll_fd = -1; diff --git a/src/epoll_shim_ctx.c b/src/epoll_shim_ctx.c index cdbd9bf..bb6e967 100644 --- a/src/epoll_shim_ctx.c +++ b/src/epoll_shim_ctx.c @@ -50,9 +50,21 @@ fd_context_map_node_destroy(FDContextMapNode *node) /**/ +errno_t +fd_context_default_read(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred) +{ + (void)node; + (void)buf; + (void)nbytes; + (void)bytes_transferred; + + return EINVAL; +} + errno_t fd_context_default_write(FDContextMapNode *node, /**/ - const void *buf, size_t nbytes, size_t *bytes_transferred) + void const *buf, size_t nbytes, size_t *bytes_transferred) { (void)node; (void)buf; diff --git a/src/epoll_shim_ctx.h b/src/epoll_shim_ctx.h index 959090b..d0497e8 100644 --- a/src/epoll_shim_ctx.h +++ b/src/epoll_shim_ctx.h @@ -5,6 +5,7 @@ #include +#include "epollfd_ctx.h" #include "eventfd_ctx.h" #include "signalfd_ctx.h" #include "timerfd_ctx.h" @@ -24,14 +25,17 @@ typedef struct { fd_context_close_fun close_fun; } FDContextVTable; +errno_t fd_context_default_read(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred); errno_t fd_context_default_write(FDContextMapNode *node, /**/ - const void *buf, size_t nbytes, size_t *bytes_transferred); + void const *buf, size_t nbytes, size_t *bytes_transferred); struct fd_context_map_node_ { RB_ENTRY(fd_context_map_node_) entry; int fd; int flags; union { + EpollFDCtx epollfd; EventFDCtx eventfd; TimerFDCtx timerfd; SignalFDCtx signalfd; diff --git a/src/epollfd_ctx.c b/src/epollfd_ctx.c new file mode 100644 index 0000000..0b90d37 --- /dev/null +++ b/src/epollfd_ctx.c @@ -0,0 +1,23 @@ +#include "epollfd_ctx.h" + +#include + +#include + +#include + +errno_t +epollfd_ctx_init(EpollFDCtx *epollfd, int kq) +{ + *epollfd = (EpollFDCtx){.kq = kq}; + + return 0; +} + +errno_t +epollfd_ctx_terminate(EpollFDCtx *epollfd) +{ + (void)epollfd; + + return 0; +} diff --git a/src/epollfd_ctx.h b/src/epollfd_ctx.h new file mode 100644 index 0000000..4245f66 --- /dev/null +++ b/src/epollfd_ctx.h @@ -0,0 +1,13 @@ +#ifndef EPOLLFD_CTX_H_ +#define EPOLLFD_CTX_H_ + +#include + +typedef struct { + int kq; // non owning +} EpollFDCtx; + +errno_t epollfd_ctx_init(EpollFDCtx *signalfd, int kq); +errno_t epollfd_ctx_terminate(EpollFDCtx *signalfd); + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3bc61a5..9410f50 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,8 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") add_executable(kqueue-state kqueue-state.c) target_include_directories(kqueue-state PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../include") + target_link_options(kqueue-state PRIVATE + "LINKER:--unresolved-symbols=ignore-all") add_test(NAME kqueue-state COMMAND kqueue-state) add_executable(eventfd-ctx-test eventfd-ctx-test.c) From c13d163abaf2dc5dbbd992bd66856cc0f9e71551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 19:50:46 +0100 Subject: [PATCH 60/75] fix cleaning up old context objects when the kq was closed with close(), not epoll_shim_close() --- src/epoll_shim_ctx.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/epoll_shim_ctx.c b/src/epoll_shim_ctx.c index bb6e967..82a3dad 100644 --- a/src/epoll_shim_ctx.c +++ b/src/epoll_shim_ctx.c @@ -29,11 +29,11 @@ fd_context_map_node_create(int kq, errno_t *ec) } static errno_t -fd_context_map_node_terminate(FDContextMapNode *node) +fd_context_map_node_terminate(FDContextMapNode *node, bool close_fd) { errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0; - if (close(node->fd) < 0) { + if (close_fd && close(node->fd) < 0) { ec = ec ? ec : errno; } @@ -43,7 +43,7 @@ fd_context_map_node_terminate(FDContextMapNode *node) errno_t fd_context_map_node_destroy(FDContextMapNode *node) { - errno_t ec = fd_context_map_node_terminate(node); + errno_t ec = fd_context_map_node_terminate(node, true); free(node); return ec; } @@ -105,7 +105,15 @@ epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq, } if (node) { - (void)fd_context_map_node_terminate(node); + /* + * If we get here, someone must have already closed the old fd + * with a normal 'close()' call, i.e. not with our + * 'epoll_shim_close()' wrapper. The fd inside the node + * refers now to the new kq we are currently creating. We + * must not close it, but we must clean up the old context + * object! + */ + (void)fd_context_map_node_terminate(node, false); fd_context_map_node_init(node, kq); } else { node = fd_context_map_node_create(kq, ec); From 7cb38bb1d420fa4fb12b7c639dd4f7ad7f52c87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 23:12:49 +0100 Subject: [PATCH 61/75] improve error handling of kqueue_load_state/kqueue_save_state --- src/epoll.c | 34 +++++++++++++++++----------------- test/kqueue-state.c | 22 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 29b8592..dd34202 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -92,17 +92,17 @@ static void *poll_ptr; #define KEY_BITS (20) #define VAL_BITS (32 - KEY_BITS) -static int +static errno_t kqueue_save_state(int kq, uint32_t key, uint16_t val) { struct kevent kev[VAL_BITS * 2]; int n = 0; int i; - int oe, e; + int oe, ec; if ((key & ~(((uint32_t)1 << KEY_BITS) - 1)) || (val & ~(((uint16_t)1 << VAL_BITS) - 1))) { - return (-EINVAL); + return (EINVAL); } for (i = 0; i < VAL_BITS; ++i) { @@ -119,25 +119,25 @@ kqueue_save_state(int kq, uint32_t key, uint16_t val) oe = errno; if ((n = kevent(kq, kev, n, NULL, 0, NULL)) < 0) { - e = errno; + ec = errno; errno = oe; - return (-e); + return (ec); } return (0); } -static int +static errno_t kqueue_load_state(int kq, uint32_t key, uint16_t *val) { struct kevent kev[VAL_BITS]; int n = 0; int i; uint16_t nval = 0; - int oe, e; + int oe, ec; if ((key & ~(((uint32_t)1 << KEY_BITS) - 1))) { - return (-EINVAL); + return (EINVAL); } for (i = 0; i < VAL_BITS; ++i) { @@ -148,20 +148,20 @@ kqueue_load_state(int kq, uint32_t key, uint16_t *val) oe = errno; if ((n = kevent(kq, kev, VAL_BITS, kev, VAL_BITS, NULL)) < 0) { - e = errno; + ec = errno; errno = oe; - return (-e); + return (ec); } for (i = 0; i < n; ++i) { if (!(kev[i].flags & EV_ERROR)) { - return (-EINVAL); + return (EINVAL); } if (kev[i].data == 0) { nval |= (uint32_t)1 << i; } else if (kev[i].data != ENOENT) { - return (-EINVAL); + return (EINVAL); } } @@ -216,7 +216,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) { struct kevent kev[2]; uint16_t flags; - int e; + errno_t ec; if ((!ev && op != EPOLL_CTL_DEL) || (ev && @@ -236,8 +236,8 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) return (-1); } - if ((e = kqueue_load_state(fd, (uint32_t)fd2, &flags)) < 0) { - errno = -e; + if ((ec = kqueue_load_state(fd, (uint32_t)fd2, &flags)) != 0) { + errno = ec; return (-1); } @@ -395,8 +395,8 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) } } - if ((e = kqueue_save_state(fd, (uint32_t)fd2, flags)) < 0) { - errno = -e; + if ((ec = kqueue_save_state(fd, (uint32_t)fd2, flags)) != 0) { + errno = ec; return (-1); } diff --git a/test/kqueue-state.c b/test/kqueue-state.c index 0df61ec..ed182b0 100644 --- a/test/kqueue-state.c +++ b/test/kqueue-state.c @@ -14,7 +14,7 @@ int main() { int kq; - int e; + errno_t ec; uint16_t retval; kq = kqueue(); @@ -22,30 +22,30 @@ main() err(1, "kqueue"); } - if ((e = kqueue_save_state(kq, 42, 0xfffu)) < 0) { - errno = -e; + if ((ec = kqueue_save_state(kq, 42, 0xfffu)) != 0) { + errno = ec; err(1, "kqueue_save_state"); } - if ((e = kqueue_save_state(kq, 42, 0xf0fu)) < 0) { - errno = -e; + if ((ec = kqueue_save_state(kq, 42, 0xf0fu)) != 0) { + errno = ec; err(1, "kqueue_save_state"); } - if ((e = kqueue_save_state(kq, 41, 0x123u)) < 0) { - errno = -e; + if ((ec = kqueue_save_state(kq, 41, 0x123u)) != 0) { + errno = ec; err(1, "kqueue_save_state"); } - if ((e = kqueue_load_state(kq, 42, &retval)) < 0) { - errno = -e; + if ((ec = kqueue_load_state(kq, 42, &retval)) != 0) { + errno = ec; err(1, "kqueue_load_state"); } fprintf(stderr, "got %x, expected %x\n", (unsigned)retval, 0xf0fu); - if ((e = kqueue_load_state(kq, 41, &retval)) < 0) { - errno = -e; + if ((ec = kqueue_load_state(kq, 41, &retval)) != 0) { + errno = ec; err(1, "kqueue_load_state"); } From f9e7fd563abe9e46ff286aa590ed11954bd7a024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 23:20:06 +0100 Subject: [PATCH 62/75] add mutex to EpollFDCtx --- src/epollfd_ctx.c | 13 +++++++++++-- src/epollfd_ctx.h | 3 +++ src/timerfd_ctx.c | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/epollfd_ctx.c b/src/epollfd_ctx.c index 0b90d37..054484d 100644 --- a/src/epollfd_ctx.c +++ b/src/epollfd_ctx.c @@ -11,13 +11,22 @@ epollfd_ctx_init(EpollFDCtx *epollfd, int kq) { *epollfd = (EpollFDCtx){.kq = kq}; + errno_t ec; + if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { + return ec; + } + return 0; } errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd) { - (void)epollfd; + errno_t ec = 0; + errno_t ec_local = 0; - return 0; + ec_local = pthread_mutex_destroy(&epollfd->mutex); + ec = ec ? ec : ec_local; + + return ec; } diff --git a/src/epollfd_ctx.h b/src/epollfd_ctx.h index 4245f66..9d89359 100644 --- a/src/epollfd_ctx.h +++ b/src/epollfd_ctx.h @@ -3,8 +3,11 @@ #include +#include + typedef struct { int kq; // non owning + pthread_mutex_t mutex; } EpollFDCtx; errno_t epollfd_ctx_init(EpollFDCtx *signalfd, int kq); diff --git a/src/timerfd_ctx.c b/src/timerfd_ctx.c index 6f8372b..949f3e1 100644 --- a/src/timerfd_ctx.c +++ b/src/timerfd_ctx.c @@ -152,7 +152,7 @@ timerfd_ctx_terminate(TimerFDCtx *timerfd) ec_local = pthread_mutex_destroy(&timerfd->mutex); ec = ec ? ec : ec_local; - return (ec); + return ec; } static errno_t @@ -300,5 +300,5 @@ timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value) ec = timerfd_ctx_read_impl(timerfd, value); (void)pthread_mutex_unlock(&timerfd->mutex); - return (ec); + return ec; } From 908fff5bca823e03a20870c8a386c2bb2ca11123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 3 Nov 2019 23:31:09 +0100 Subject: [PATCH 63/75] remove poll hack for now --- src/epoll.c | 38 +++++--------------------------------- test/epoll-test.c | 6 +++--- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index dd34202..7dbb10f 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -86,10 +86,6 @@ epoll_create1(int flags) return epoll_create_common(); } -static int poll_fd = -1; -static int poll_epoll_fd = -1; -static void *poll_ptr; - #define KEY_BITS (20) #define VAL_BITS (32 - KEY_BITS) static errno_t @@ -270,12 +266,7 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) #undef SET_FLAG } else if (op == EPOLL_CTL_DEL) { - if (poll_fd == fd2 && fd == poll_epoll_fd) { - poll_fd = -1; - poll_epoll_fd = -1; - poll_ptr = NULL; - return (0); - } + /* Removed poll fd here. */ if (!(flags & KQUEUE_STATE_REGISTERED)) { errno = ENOENT; @@ -339,12 +330,8 @@ epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) return (-1); } - if (kev[i].data == ENODEV && poll_fd < 0) { - poll_fd = fd2; - poll_epoll_fd = fd; - poll_ptr = ev->data.ptr; - return (0); - } + /* Checked for 'kev[i].data == ENODEV' here (for fds that only + * support poll). */ /* * Ignore EVFILT_WRITE registration EINVAL errors (some fd @@ -428,23 +415,8 @@ epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) return -1; } - if (poll_fd != -1 && fd == poll_epoll_fd) { - struct pollfd pfds[2]; - pfds[0].fd = poll_fd; - pfds[0].events = POLLIN; - pfds[1].fd = fd; - pfds[1].events = POLLIN; - int ret = poll(pfds, 2, to); - if (ret <= 0) { - return ret; - } - if (pfds[0].revents & POLLIN) { - ev[0].events = EPOLLIN; - ev[0].data.ptr = poll_ptr; - return 1; - } - to = 0; - } + /* Top level poll call was here (to support registered fd's that only + * support poll). */ struct timespec timeout = {0, 0}; if (to > 0) { diff --git a/test/epoll-test.c b/test/epoll-test.c index 9573248..979a28b 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -506,14 +506,14 @@ test11() return -1; } - int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); + int fd = open("/dev/dsp", O_WRONLY | O_CLOEXEC); if (fd < 0) { - // Don't fail the test when there is no graphics card. + // Don't fail the test when there is no sound card. goto out; } struct epoll_event event; - event.events = EPOLLIN | EPOLLOUT; + event.events = EPOLLOUT; event.data.fd = fd; if (epoll_ctl(ep, EPOLL_CTL_ADD, fd, &event) < 0) { From a3e7dddab2a803c7a186db4ae83c2827f2a126ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 4 Nov 2019 00:35:50 +0100 Subject: [PATCH 64/75] move epollfd implementation details into epollfd_ctx.c --- src/epoll.c | 551 ++++++------------------------------------- src/epollfd_ctx.c | 554 +++++++++++++++++++++++++++++++++++++++++++- src/epollfd_ctx.h | 27 ++- test/kqueue-state.c | 2 +- 4 files changed, 651 insertions(+), 483 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 7dbb10f..a54023f 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -1,9 +1,9 @@ #include #include -#include -#include +#include +#include #include #include #include @@ -86,313 +86,26 @@ epoll_create1(int flags) return epoll_create_common(); } -#define KEY_BITS (20) -#define VAL_BITS (32 - KEY_BITS) -static errno_t -kqueue_save_state(int kq, uint32_t key, uint16_t val) -{ - struct kevent kev[VAL_BITS * 2]; - int n = 0; - int i; - int oe, ec; - - if ((key & ~(((uint32_t)1 << KEY_BITS) - 1)) || - (val & ~(((uint16_t)1 << VAL_BITS) - 1))) { - return (EINVAL); - } - - for (i = 0; i < VAL_BITS; ++i) { - uint32_t info_bit = (uint32_t)1 << i; - uint32_t kev_key = key | (info_bit << KEY_BITS); - EV_SET(&kev[n], kev_key, EVFILT_USER, EV_ADD, 0, 0, 0); - ++n; - if (!(val & info_bit)) { - EV_SET(&kev[n], kev_key, EVFILT_USER, /**/ - EV_DELETE, 0, 0, 0); - ++n; - } - } - - oe = errno; - if ((n = kevent(kq, kev, n, NULL, 0, NULL)) < 0) { - ec = errno; - errno = oe; - return (ec); - } - - return (0); -} - -static errno_t -kqueue_load_state(int kq, uint32_t key, uint16_t *val) -{ - struct kevent kev[VAL_BITS]; - int n = 0; - int i; - uint16_t nval = 0; - int oe, ec; - - if ((key & ~(((uint32_t)1 << KEY_BITS) - 1))) { - return (EINVAL); - } - - for (i = 0; i < VAL_BITS; ++i) { - uint32_t info_bit = (uint32_t)1 << i; - uint32_t kev_key = key | (info_bit << KEY_BITS); - EV_SET(&kev[i], kev_key, EVFILT_USER, EV_RECEIPT, 0, 0, 0); - } - - oe = errno; - if ((n = kevent(kq, kev, VAL_BITS, kev, VAL_BITS, NULL)) < 0) { - ec = errno; - errno = oe; - return (ec); - } - - for (i = 0; i < n; ++i) { - if (!(kev[i].flags & EV_ERROR)) { - return (EINVAL); - } - - if (kev[i].data == 0) { - nval |= (uint32_t)1 << i; - } else if (kev[i].data != ENOENT) { - return (EINVAL); - } - } - - *val = nval; - - return (0); -} - -#define KQUEUE_STATE_REGISTERED 0x1u -#define KQUEUE_STATE_EPOLLIN 0x2u -#define KQUEUE_STATE_EPOLLOUT 0x4u -#define KQUEUE_STATE_EPOLLRDHUP 0x8u -#define KQUEUE_STATE_NYCSS 0x10u -#define KQUEUE_STATE_ISFIFO 0x20u -#define KQUEUE_STATE_ISSOCK 0x40u - -static int -is_not_yet_connected_stream_socket(int s) -{ - - { - int val; - socklen_t length = sizeof(int); - - if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, /**/ - &val, &length) == 0 && - val) { - return 0; - } - } - - { - int type; - socklen_t length = sizeof(int); - - if (getsockopt(s, SOL_SOCKET, SO_TYPE, &type, &length) == 0 && - (type == SOCK_STREAM || type == SOCK_SEQPACKET)) { - struct sockaddr name; - socklen_t namelen = 0; - if (getpeername(s, &name, &namelen) < 0 && - errno == ENOTCONN) { - return 1; - } - } - } - - return 0; -} - int epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) { - struct kevent kev[2]; - uint16_t flags; errno_t ec; + FDContextMapNode *node; - if ((!ev && op != EPOLL_CTL_DEL) || - (ev && - ((ev->events & - ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLHUP /**/ - | EPOLLRDHUP | EPOLLERR)) - /* the user should really set one of EPOLLIN or EPOLLOUT - * so that EPOLLHUP and EPOLLERR work. Don't make this a - * hard error for now, though. */ - /* || !(ev->events & (EPOLLIN | EPOLLOUT)) */))) { - errno = EINVAL; - return (-1); - } - - if (fd2 < 0 || ((uint32_t)fd2 & ~(((uint32_t)1 << KEY_BITS) - 1))) { - errno = EBADF; - return (-1); - } - - if ((ec = kqueue_load_state(fd, (uint32_t)fd2, &flags)) != 0) { - errno = ec; - return (-1); - } - - if (op == EPOLL_CTL_ADD) { - if (flags & KQUEUE_STATE_REGISTERED) { - errno = EEXIST; - return (-1); - } - - EV_SET(&kev[0], fd2, EVFILT_READ, - EV_ADD | (ev->events & EPOLLIN ? 0 : EV_DISABLE), 0, 0, - ev->data.ptr); - EV_SET(&kev[1], fd2, EVFILT_WRITE, - EV_ADD | (ev->events & EPOLLOUT ? 0 : EV_DISABLE), 0, 0, - ev->data.ptr); - - flags = KQUEUE_STATE_REGISTERED; - -#define SET_FLAG(flag) \ - do { \ - if (ev->events & (flag)) { \ - flags |= KQUEUE_STATE_##flag; \ - } \ - } while (0) - - SET_FLAG(EPOLLIN); - SET_FLAG(EPOLLOUT); - SET_FLAG(EPOLLRDHUP); - -#undef SET_FLAG - - } else if (op == EPOLL_CTL_DEL) { - /* Removed poll fd here. */ - - if (!(flags & KQUEUE_STATE_REGISTERED)) { - errno = ENOENT; - return (-1); - } - - EV_SET(&kev[0], fd2, EVFILT_READ, EV_DELETE, 0, 0, 0); - EV_SET(&kev[1], fd2, EVFILT_WRITE, EV_DELETE, 0, 0, 0); - - flags = 0; - } else if (op == EPOLL_CTL_MOD) { - if (!(flags & KQUEUE_STATE_REGISTERED)) { - errno = ENOENT; - return (-1); - } - - EV_SET(&kev[0], fd2, EVFILT_READ, - ev->events & EPOLLIN ? EV_ENABLE : EV_DISABLE, 0, 0, - ev->data.ptr); - EV_SET(&kev[1], fd2, EVFILT_WRITE, - ev->events & EPOLLOUT ? EV_ENABLE : EV_DISABLE, 0, 0, - ev->data.ptr); - -#define SET_FLAG(flag) \ - do { \ - if (ev->events & (flag)) { \ - flags |= KQUEUE_STATE_##flag; \ - } else { \ - flags &= ~KQUEUE_STATE_##flag; \ - } \ - } while (0) - - SET_FLAG(EPOLLIN); - SET_FLAG(EPOLLOUT); - SET_FLAG(EPOLLRDHUP); - -#undef SET_FLAG - - } else { - errno = EINVAL; - return (-1); - } - - for (int i = 0; i < 2; ++i) { - kev[i].flags |= EV_RECEIPT; - } - - int ret = kevent(fd, kev, 2, kev, 2, NULL); - if (ret < 0) { - return (-1); - } - - if (ret != 2) { + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &epollfd_vtable) { errno = EINVAL; - return (-1); - } - - for (int i = 0; i < 2; ++i) { - if (!(kev[i].flags & EV_ERROR)) { - errno = EINVAL; - return (-1); - } - - /* Checked for 'kev[i].data == ENODEV' here (for fds that only - * support poll). */ - - /* - * Ignore EVFILT_WRITE registration EINVAL errors (some fd - * types such as kqueues themselves don't support it). - * Also ignore ENOENT -- this happens when trying to remove a - * previously added fd where the EVFILT_WRITE registration - * failed. - */ - if (i == 1 && - (kev[i].data == EINVAL || kev[i].data == ENOENT)) { - continue; - } - - if (kev[i].data != 0) { - if (i == 0 && - (kev[i].data == ENOENT || kev[i].data == EBADF)) { - kqueue_save_state(fd, (uint32_t)fd2, 0); - } - errno = (int)kev[i].data; - return (-1); - } - } - - if (op != EPOLL_CTL_DEL && is_not_yet_connected_stream_socket(fd2)) { - EV_SET(&kev[0], fd2, EVFILT_READ, EV_ENABLE | EV_FORCEONESHOT, - 0, 0, ev->data.ptr); - if (kevent(fd, kev, 1, NULL, 0, NULL) < 0) { - return (-1); - } - - flags |= KQUEUE_STATE_NYCSS; - } - - if (op == EPOLL_CTL_ADD) { - struct stat statbuf; - if (fstat(fd2, &statbuf) < 0) { - /* If the fstat fails for some reason we must clear - * internal state to avoid EEXIST errors in future - * calls to epoll_ctl. */ - (void)kqueue_save_state(fd, (uint32_t)fd2, 0); - return (-1); - } - - if (S_ISFIFO(statbuf.st_mode)) { - flags |= KQUEUE_STATE_ISFIFO; - } else if (S_ISSOCK(statbuf.st_mode)) { - flags |= KQUEUE_STATE_ISSOCK; - } + return -1; } - if ((ec = kqueue_save_state(fd, (uint32_t)fd2, flags)) != 0) { + if ((ec = epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev)) != 0) { errno = ec; - return (-1); + return -1; } - return (0); + return 0; } -#undef VAL_BITS -#undef KEY_BITS - #if 0 int epoll_pwait( @@ -407,205 +120,87 @@ epoll_pwait( } #endif -int -epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) +static errno_t +epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt, int to) { - if (cnt < 1 || cnt > 32) { - errno = EINVAL; - return -1; - } + struct timespec deadline; - /* Top level poll call was here (to support registered fd's that only - * support poll). */ - - struct timespec timeout = {0, 0}; if (to > 0) { - timeout.tv_sec = to / 1000; - timeout.tv_nsec = (to % 1000) * 1000 * 1000; - } + if (clock_gettime(CLOCK_MONOTONIC, &deadline) < 0) { + return errno; + } - struct timespec *ptimeout = NULL; - if (to >= 0) { - ptimeout = &timeout; - } + if (__builtin_add_overflow(deadline.tv_sec, to / 1000 + 1, + &deadline.tv_sec)) { + return EINVAL; + } + deadline.tv_sec -= 1; -again:; - struct kevent evlist[32]; - int ret = kevent(fd, NULL, 0, evlist, cnt, ptimeout); - if (ret < 0) { - return -1; + deadline.tv_nsec += (to % 1000) * 1000000L; + if (deadline.tv_nsec >= 1000000000) { + deadline.tv_nsec -= 1000000000; + deadline.tv_sec += 1; + } } - int j = 0; - - for (int i = 0; i < ret; ++i) { - int events = 0; - if (evlist[i].filter == EVFILT_READ) { - events |= EPOLLIN; - if (evlist[i].flags & EV_ONESHOT) { - uint16_t flags = 0; - kqueue_load_state(fd, - (uint32_t)evlist[i].ident, &flags); - - if (flags & KQUEUE_STATE_NYCSS) { - if (is_not_yet_connected_stream_socket( - (int)evlist[i].ident)) { - - events = EPOLLHUP; - if (flags & - KQUEUE_STATE_EPOLLOUT) { - events |= EPOLLOUT; - } - - struct kevent nkev[2]; - EV_SET(&nkev[0], - evlist[i].ident, - EVFILT_READ, EV_ADD, /**/ - 0, 0, evlist[i].udata); - EV_SET(&nkev[1], - evlist[i].ident, - EVFILT_READ, - EV_ENABLE | - EV_FORCEONESHOT, - 0, 0, evlist[i].udata); - - kevent(fd, nkev, 2, NULL, 0, - NULL); - } else { - flags &= ~KQUEUE_STATE_NYCSS; - - struct kevent nkev[2]; - EV_SET(&nkev[0], - evlist[i].ident, - EVFILT_READ, EV_ADD, /**/ - 0, 0, evlist[i].udata); - EV_SET(&nkev[1], - evlist[i].ident, - EVFILT_READ, - flags & KQUEUE_STATE_EPOLLIN - ? EV_ENABLE - : EV_DISABLE, - 0, 0, evlist[i].udata); - - kevent(fd, nkev, 2, NULL, 0, - NULL); - kqueue_save_state(fd, - (uint32_t)evlist[i].ident, - flags); - - continue; - } - } - } - } else if (evlist[i].filter == EVFILT_WRITE) { - events |= EPOLLOUT; + for (;;) { + errno_t ec = epollfd_ctx_wait(epollfd, ev, cnt, actual_cnt); + if (ec || *actual_cnt || to == 0) { + return ec; } - if (evlist[i].flags & EV_ERROR) { - events |= EPOLLERR; - } + struct timespec current_time; + struct timespec timeout; - if (evlist[i].flags & EV_EOF) { - if (evlist[i].fflags) { - events |= EPOLLERR; + if (to > 0) { + if (clock_gettime(CLOCK_MONOTONIC, /**/ + ¤t_time) < 0) { + return errno; } - uint16_t flags = 0; - kqueue_load_state(fd, (uint32_t)evlist[i].ident, - &flags); - - int epoll_event; - - if (flags & KQUEUE_STATE_ISFIFO) { - if (evlist[i].filter == EVFILT_READ) { - epoll_event = EPOLLHUP; - if (evlist[i].data == 0) { - events &= ~EPOLLIN; - } - } else if (evlist[i].filter == EVFILT_WRITE) { - epoll_event = EPOLLERR; - } else { - /* should not happen */ - return -1; - } - } else if (flags & KQUEUE_STATE_ISSOCK) { - if (evlist[i].filter == EVFILT_READ) { - /* do some special EPOLLRDHUP handling - * for sockets */ - - /* if we are reading, we just know for - * sure that we can't receive any more, - * so use EPOLLIN/EPOLLRDHUP per - * default */ - epoll_event = EPOLLIN; - - if (flags & KQUEUE_STATE_EPOLLRDHUP) { - epoll_event |= EPOLLRDHUP; - } - } else if (evlist[i].filter == EVFILT_WRITE) { - epoll_event = EPOLLOUT; - } else { - /* should not happen */ - return -1; - } - - struct pollfd pfd = { - .fd = (int)evlist[i].ident, - .events = POLLIN | POLLOUT | POLLHUP, - }; - - if (poll(&pfd, 1, 0) == 1) { - if (pfd.revents & POLLHUP) { - /* - * We need to set these flags - * so that readers still have a - * chance to read the last data - * from the socket. This is - * very important to preserve - * Linux poll/epoll semantics - * when coming from an - * EVFILT_WRITE event. - */ - if (flags & - KQUEUE_STATE_EPOLLIN) { - epoll_event |= EPOLLIN; - } - if (flags & - KQUEUE_STATE_EPOLLRDHUP) { - epoll_event |= - EPOLLRDHUP; - } - - epoll_event |= EPOLLHUP; - } - - /* might as well steal flags from the - * poll call while we're here */ - - if ((pfd.revents & POLLIN) && - (flags & KQUEUE_STATE_EPOLLIN)) { - epoll_event |= EPOLLIN; - } - - if ((pfd.revents & POLLOUT) && - (flags & KQUEUE_STATE_EPOLLOUT)) { - epoll_event |= EPOLLOUT; - } - } - } else { - epoll_event = EPOLLHUP; + timespecsub(&deadline, ¤t_time, &timeout); + if (timeout.tv_sec < 0) { + timeout.tv_sec = 0; + timeout.tv_nsec = 0; } - events |= epoll_event; + to = (int)((timeout.tv_sec * 1000) + + (timeout.tv_nsec / 1000000) + + !!(timeout.tv_nsec % 1000000)); + + if (to == 0) { + continue; + } + } + + assert(to > 0); + + struct pollfd pfd = {.fd = epollfd->kq, .events = POLLIN}; + if (poll(&pfd, 1, to) < 0) { + return errno; } - ev[j].events = (uint32_t)events; - ev[j].data.ptr = evlist[i].udata; - ++j; } +} - if (ret && j == 0) { - goto again; +int +epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) +{ + errno_t ec; + FDContextMapNode *node; + + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &epollfd_vtable) { + errno = EINVAL; + return -1; + } + + int actual_cnt; + if ((ec = epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt, + &actual_cnt, to)) != 0) { + errno = ec; + return -1; } - return j; + return actual_cnt; } diff --git a/src/epollfd_ctx.c b/src/epollfd_ctx.c index 054484d..3a00202 100644 --- a/src/epollfd_ctx.c +++ b/src/epollfd_ctx.c @@ -3,15 +3,31 @@ #include #include +#include +#include #include +#include + +static int +fd_cmp(RegisteredFDsNode *e1, RegisteredFDsNode *e2) +{ + return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); +} + +RB_GENERATE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp) + errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq) { - *epollfd = (EpollFDCtx){.kq = kq}; - errno_t ec; + + *epollfd = (EpollFDCtx){ + .kq = kq, + .registered_fds = RB_INITIALIZER(®istered_fds), + }; + if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { return ec; } @@ -30,3 +46,537 @@ epollfd_ctx_terminate(EpollFDCtx *epollfd) return ec; } + +#define KEY_BITS (20) +#define VAL_BITS (32 - KEY_BITS) +static errno_t +kqueue_save_state(int kq, uint32_t key, uint16_t val) +{ + struct kevent kev[VAL_BITS * 2]; + int n = 0; + int i; + int oe, ec; + + if ((key & ~(((uint32_t)1 << KEY_BITS) - 1)) || + (val & ~(((uint16_t)1 << VAL_BITS) - 1))) { + return (EINVAL); + } + + for (i = 0; i < VAL_BITS; ++i) { + uint32_t info_bit = (uint32_t)1 << i; + uint32_t kev_key = key | (info_bit << KEY_BITS); + EV_SET(&kev[n], kev_key, EVFILT_USER, EV_ADD, 0, 0, 0); + ++n; + if (!(val & info_bit)) { + EV_SET(&kev[n], kev_key, EVFILT_USER, /**/ + EV_DELETE, 0, 0, 0); + ++n; + } + } + + oe = errno; + if ((n = kevent(kq, kev, n, NULL, 0, NULL)) < 0) { + ec = errno; + errno = oe; + return (ec); + } + + return (0); +} + +static errno_t +kqueue_load_state(int kq, uint32_t key, uint16_t *val) +{ + struct kevent kev[VAL_BITS]; + int n = 0; + int i; + uint16_t nval = 0; + int oe, ec; + + if ((key & ~(((uint32_t)1 << KEY_BITS) - 1))) { + return (EINVAL); + } + + for (i = 0; i < VAL_BITS; ++i) { + uint32_t info_bit = (uint32_t)1 << i; + uint32_t kev_key = key | (info_bit << KEY_BITS); + EV_SET(&kev[i], kev_key, EVFILT_USER, EV_RECEIPT, 0, 0, 0); + } + + oe = errno; + if ((n = kevent(kq, kev, VAL_BITS, kev, VAL_BITS, NULL)) < 0) { + ec = errno; + errno = oe; + return (ec); + } + + for (i = 0; i < n; ++i) { + if (!(kev[i].flags & EV_ERROR)) { + return (EINVAL); + } + + if (kev[i].data == 0) { + nval |= (uint32_t)1 << i; + } else if (kev[i].data != ENOENT) { + return (EINVAL); + } + } + + *val = nval; + + return (0); +} + +#define KQUEUE_STATE_REGISTERED 0x1u +#define KQUEUE_STATE_EPOLLIN 0x2u +#define KQUEUE_STATE_EPOLLOUT 0x4u +#define KQUEUE_STATE_EPOLLRDHUP 0x8u +#define KQUEUE_STATE_NYCSS 0x10u +#define KQUEUE_STATE_ISFIFO 0x20u +#define KQUEUE_STATE_ISSOCK 0x40u + +static int +is_not_yet_connected_stream_socket(int s) +{ + + { + int val; + socklen_t length = sizeof(int); + + if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, /**/ + &val, &length) == 0 && + val) { + return 0; + } + } + + { + int type; + socklen_t length = sizeof(int); + + if (getsockopt(s, SOL_SOCKET, SO_TYPE, &type, &length) == 0 && + (type == SOCK_STREAM || type == SOCK_SEQPACKET)) { + struct sockaddr name; + socklen_t namelen = 0; + if (getpeername(s, &name, &namelen) < 0 && + errno == ENOTCONN) { + return 1; + } + } + } + + return 0; +} + +static errno_t +epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, + struct epoll_event *ev) +{ + struct kevent kev[2]; + uint16_t flags; + errno_t ec; + + if ((!ev && op != EPOLL_CTL_DEL) || + (ev && + ((ev->events & + ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLHUP /**/ + | EPOLLRDHUP | EPOLLERR)) + /* the user should really set one of EPOLLIN or EPOLLOUT + * so that EPOLLHUP and EPOLLERR work. Don't make this a + * hard error for now, though. */ + /* || !(ev->events & (EPOLLIN | EPOLLOUT)) */))) { + return EINVAL; + } + + if (fd2 < 0 || ((uint32_t)fd2 & ~(((uint32_t)1 << KEY_BITS) - 1))) { + return EBADF; + } + + if ((ec = kqueue_load_state(epollfd->kq, /**/ + (uint32_t)fd2, &flags)) != 0) { + return ec; + } + + if (op == EPOLL_CTL_ADD) { + if (flags & KQUEUE_STATE_REGISTERED) { + return EEXIST; + } + + EV_SET(&kev[0], fd2, EVFILT_READ, + EV_ADD | (ev->events & EPOLLIN ? 0 : EV_DISABLE), 0, 0, + ev->data.ptr); + EV_SET(&kev[1], fd2, EVFILT_WRITE, + EV_ADD | (ev->events & EPOLLOUT ? 0 : EV_DISABLE), 0, 0, + ev->data.ptr); + + flags = KQUEUE_STATE_REGISTERED; + +#define SET_FLAG(flag) \ + do { \ + if (ev->events & (flag)) { \ + flags |= KQUEUE_STATE_##flag; \ + } \ + } while (0) + + SET_FLAG(EPOLLIN); + SET_FLAG(EPOLLOUT); + SET_FLAG(EPOLLRDHUP); + +#undef SET_FLAG + + } else if (op == EPOLL_CTL_DEL) { + /* Removed poll fd here. */ + + if (!(flags & KQUEUE_STATE_REGISTERED)) { + return ENOENT; + } + + EV_SET(&kev[0], fd2, EVFILT_READ, EV_DELETE, 0, 0, 0); + EV_SET(&kev[1], fd2, EVFILT_WRITE, EV_DELETE, 0, 0, 0); + + flags = 0; + } else if (op == EPOLL_CTL_MOD) { + if (!(flags & KQUEUE_STATE_REGISTERED)) { + return ENOENT; + } + + EV_SET(&kev[0], fd2, EVFILT_READ, + ev->events & EPOLLIN ? EV_ENABLE : EV_DISABLE, 0, 0, + ev->data.ptr); + EV_SET(&kev[1], fd2, EVFILT_WRITE, + ev->events & EPOLLOUT ? EV_ENABLE : EV_DISABLE, 0, 0, + ev->data.ptr); + +#define SET_FLAG(flag) \ + do { \ + if (ev->events & (flag)) { \ + flags |= KQUEUE_STATE_##flag; \ + } else { \ + flags &= ~KQUEUE_STATE_##flag; \ + } \ + } while (0) + + SET_FLAG(EPOLLIN); + SET_FLAG(EPOLLOUT); + SET_FLAG(EPOLLRDHUP); + +#undef SET_FLAG + + } else { + return EINVAL; + } + + for (int i = 0; i < 2; ++i) { + kev[i].flags |= EV_RECEIPT; + } + + int ret = kevent(epollfd->kq, kev, 2, kev, 2, NULL); + if (ret < 0) { + return errno; + } + + if (ret != 2) { + return EINVAL; + } + + for (int i = 0; i < 2; ++i) { + if (!(kev[i].flags & EV_ERROR)) { + return EINVAL; + } + + /* Checked for 'kev[i].data == ENODEV' here (for fds that only + * support poll). */ + + /* + * Ignore EVFILT_WRITE registration EINVAL errors (some fd + * types such as kqueues themselves don't support it). + * Also ignore ENOENT -- this happens when trying to remove a + * previously added fd where the EVFILT_WRITE registration + * failed. + */ + if (i == 1 && + (kev[i].data == EINVAL || kev[i].data == ENOENT)) { + continue; + } + + if (kev[i].data != 0) { + if (i == 0 && + (kev[i].data == ENOENT || kev[i].data == EBADF)) { + kqueue_save_state(epollfd->kq, (uint32_t)fd2, + 0); + } + return (int)kev[i].data; + } + } + + if (op != EPOLL_CTL_DEL && is_not_yet_connected_stream_socket(fd2)) { + EV_SET(&kev[0], fd2, EVFILT_READ, EV_ENABLE | EV_FORCEONESHOT, + 0, 0, ev->data.ptr); + if (kevent(epollfd->kq, kev, 1, NULL, 0, NULL) < 0) { + return errno; + } + + flags |= KQUEUE_STATE_NYCSS; + } + + if (op == EPOLL_CTL_ADD) { + struct stat statbuf; + if (fstat(fd2, &statbuf) < 0) { + ec = errno; + /* If the fstat fails for some reason we must clear + * internal state to avoid EEXIST errors in future + * calls to epoll_ctl. */ + (void)kqueue_save_state(epollfd->kq, (uint32_t)fd2, 0); + return ec; + } + + if (S_ISFIFO(statbuf.st_mode)) { + flags |= KQUEUE_STATE_ISFIFO; + } else if (S_ISSOCK(statbuf.st_mode)) { + flags |= KQUEUE_STATE_ISSOCK; + } + } + + if ((ec = kqueue_save_state(epollfd->kq, (uint32_t)fd2, flags)) != 0) { + return ec; + } + + return 0; +} + +#undef VAL_BITS +#undef KEY_BITS + +errno_t +epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) +{ + errno_t ec; + + (void)pthread_mutex_lock(&epollfd->mutex); + ec = epollfd_ctx_ctl_impl(epollfd, op, fd2, ev); + (void)pthread_mutex_unlock(&epollfd->mutex); + + return ec; +} + +static errno_t +epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt) +{ + if (cnt < 1 || cnt > 32) { + return EINVAL; + } + + /* Top level poll call was here (to support registered fd's that only + * support poll). */ + +#ifdef notyet + struct timespec timeout = {0, 0}; + if (to > 0) { + timeout.tv_sec = to / 1000; + timeout.tv_nsec = (to % 1000) * 1000 * 1000; + } + + struct timespec *ptimeout = NULL; + if (to >= 0) { + ptimeout = &timeout; + } +#endif + +again:; + struct kevent evlist[32]; + int ret = kevent(epollfd->kq, NULL, 0, evlist, cnt, + &(struct timespec){0, 0}); + if (ret < 0) { + return errno; + } + + int j = 0; + + for (int i = 0; i < ret; ++i) { + int events = 0; + if (evlist[i].filter == EVFILT_READ) { + events |= EPOLLIN; + if (evlist[i].flags & EV_ONESHOT) { + uint16_t flags = 0; + kqueue_load_state(epollfd->kq, + (uint32_t)evlist[i].ident, &flags); + + if (flags & KQUEUE_STATE_NYCSS) { + if (is_not_yet_connected_stream_socket( + (int)evlist[i].ident)) { + + events = EPOLLHUP; + if (flags & + KQUEUE_STATE_EPOLLOUT) { + events |= EPOLLOUT; + } + + struct kevent nkev[2]; + EV_SET(&nkev[0], + evlist[i].ident, + EVFILT_READ, EV_ADD, /**/ + 0, 0, evlist[i].udata); + EV_SET(&nkev[1], + evlist[i].ident, + EVFILT_READ, + EV_ENABLE | + EV_FORCEONESHOT, + 0, 0, evlist[i].udata); + + kevent(epollfd->kq, nkev, 2, + NULL, 0, NULL); + } else { + flags &= ~KQUEUE_STATE_NYCSS; + + struct kevent nkev[2]; + EV_SET(&nkev[0], + evlist[i].ident, + EVFILT_READ, EV_ADD, /**/ + 0, 0, evlist[i].udata); + EV_SET(&nkev[1], + evlist[i].ident, + EVFILT_READ, + flags & KQUEUE_STATE_EPOLLIN + ? EV_ENABLE + : EV_DISABLE, + 0, 0, evlist[i].udata); + + kevent(epollfd->kq, nkev, 2, + NULL, 0, NULL); + kqueue_save_state(epollfd->kq, + (uint32_t)evlist[i].ident, + flags); + + continue; + } + } + } + } else if (evlist[i].filter == EVFILT_WRITE) { + events |= EPOLLOUT; + } + + if (evlist[i].flags & EV_ERROR) { + events |= EPOLLERR; + } + + if (evlist[i].flags & EV_EOF) { + if (evlist[i].fflags) { + events |= EPOLLERR; + } + + uint16_t flags = 0; + kqueue_load_state(epollfd->kq, + (uint32_t)evlist[i].ident, &flags); + + int epoll_event; + + if (flags & KQUEUE_STATE_ISFIFO) { + if (evlist[i].filter == EVFILT_READ) { + epoll_event = EPOLLHUP; + if (evlist[i].data == 0) { + events &= ~EPOLLIN; + } + } else if (evlist[i].filter == EVFILT_WRITE) { + epoll_event = EPOLLERR; + } else { + /* should not happen */ + assert(0); + return EIO; + } + } else if (flags & KQUEUE_STATE_ISSOCK) { + if (evlist[i].filter == EVFILT_READ) { + /* do some special EPOLLRDHUP handling + * for sockets */ + + /* if we are reading, we just know for + * sure that we can't receive any more, + * so use EPOLLIN/EPOLLRDHUP per + * default */ + epoll_event = EPOLLIN; + + if (flags & KQUEUE_STATE_EPOLLRDHUP) { + epoll_event |= EPOLLRDHUP; + } + } else if (evlist[i].filter == EVFILT_WRITE) { + epoll_event = EPOLLOUT; + } else { + /* should not happen */ + assert(0); + return EIO; + } + + struct pollfd pfd = { + .fd = (int)evlist[i].ident, + .events = POLLIN | POLLOUT | POLLHUP, + }; + + if (poll(&pfd, 1, 0) == 1) { + if (pfd.revents & POLLHUP) { + /* + * We need to set these flags + * so that readers still have a + * chance to read the last data + * from the socket. This is + * very important to preserve + * Linux poll/epoll semantics + * when coming from an + * EVFILT_WRITE event. + */ + if (flags & + KQUEUE_STATE_EPOLLIN) { + epoll_event |= EPOLLIN; + } + if (flags & + KQUEUE_STATE_EPOLLRDHUP) { + epoll_event |= + EPOLLRDHUP; + } + + epoll_event |= EPOLLHUP; + } + + /* might as well steal flags from the + * poll call while we're here */ + + if ((pfd.revents & POLLIN) && + (flags & KQUEUE_STATE_EPOLLIN)) { + epoll_event |= EPOLLIN; + } + + if ((pfd.revents & POLLOUT) && + (flags & KQUEUE_STATE_EPOLLOUT)) { + epoll_event |= EPOLLOUT; + } + } + } else { + epoll_event = EPOLLHUP; + } + + events |= epoll_event; + } + ev[j].events = (uint32_t)events; + ev[j].data.ptr = evlist[i].udata; + ++j; + } + + if (ret && j == 0) { + goto again; + } + + *actual_cnt = j; + return 0; +} + +errno_t +epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt) +{ + errno_t ec; + + (void)pthread_mutex_lock(&epollfd->mutex); + ec = epollfd_ctx_wait_impl(epollfd, ev, cnt, actual_cnt); + (void)pthread_mutex_unlock(&epollfd->mutex); + + return ec; +} diff --git a/src/epollfd_ctx.h b/src/epollfd_ctx.h index 9d89359..d2c427a 100644 --- a/src/epollfd_ctx.h +++ b/src/epollfd_ctx.h @@ -1,16 +1,39 @@ #ifndef EPOLLFD_CTX_H_ #define EPOLLFD_CTX_H_ +#define SHIM_SYS_SHIM_HELPERS +#include + +#include + #include +#include #include +struct registered_fds_node_; +typedef struct registered_fds_node_ RegisteredFDsNode; + +struct registered_fds_node_ { + RB_ENTRY(registered_fds_node_) entry; + int fd; + uint16_t flags; +}; + +typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet; + typedef struct { int kq; // non owning pthread_mutex_t mutex; + RegisteredFDsSet registered_fds; } EpollFDCtx; -errno_t epollfd_ctx_init(EpollFDCtx *signalfd, int kq); -errno_t epollfd_ctx_terminate(EpollFDCtx *signalfd); +errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq); +errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd); + +errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, + struct epoll_event *ev); +errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt); #endif diff --git a/test/kqueue-state.c b/test/kqueue-state.c index ed182b0..cf6be5d 100644 --- a/test/kqueue-state.c +++ b/test/kqueue-state.c @@ -8,7 +8,7 @@ #include -#include "../src/epoll.c" +#include "../src/epollfd_ctx.c" int main() From dbb2624bd891cb3c971092ebd0c0c31b571f2fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 4 Nov 2019 00:36:36 +0100 Subject: [PATCH 65/75] fix assertion --- src/epoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epoll.c b/src/epoll.c index a54023f..14991a1 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -174,7 +174,7 @@ epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, } } - assert(to > 0); + assert(to != 0); struct pollfd pfd = {.fd = epollfd->kq, .events = POLLIN}; if (poll(&pfd, 1, to) < 0) { From e4c43936c805a42fb2dba9f09c33aad9fee0d27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 4 Nov 2019 01:07:08 +0100 Subject: [PATCH 66/75] update sys/epoll.h header from musl --- include/sys/epoll.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/include/sys/epoll.h b/include/sys/epoll.h index 7d3af4c..8ecac06 100644 --- a/include/sys/epoll.h +++ b/include/sys/epoll.h @@ -5,11 +5,9 @@ extern "C" { #endif -/* include the same file as musl */ -#include /* IWYU pragma: keep */ - -#include #include +#include +#include #if 0 #define __NEED_sigset_t @@ -25,6 +23,7 @@ enum EPOLL_EVENTS { __EPOLL_DUMMY }; #define EPOLLPRI 0x002 #define EPOLLOUT 0x004 #define EPOLLRDNORM 0x040 +#define EPOLLNVAL 0x020 #define EPOLLRDBAND 0x080 #define EPOLLWRNORM 0x100 #define EPOLLWRBAND 0x200 @@ -32,6 +31,7 @@ enum EPOLL_EVENTS { __EPOLL_DUMMY }; #define EPOLLERR 0x008 #define EPOLLHUP 0x010 #define EPOLLRDHUP 0x2000 +#define EPOLLEXCLUSIVE (1U<<28) #define EPOLLWAKEUP (1U<<29) #define EPOLLONESHOT (1U<<30) #define EPOLLET (1U<<31) @@ -51,17 +51,16 @@ struct epoll_event { uint32_t events; epoll_data_t data; } -#if defined(__amd64__) -__attribute__((packed)) +#ifdef __x86_64__ +__attribute__ ((__packed__)) #endif ; -int epoll_create(int /*size*/); -int epoll_create1(int /*flags*/); -int epoll_ctl( - int /*fd*/, int /*op*/, int /*fd2*/, struct epoll_event * /*ev*/); -int epoll_wait( - int /*fd*/, struct epoll_event * /*ev*/, int /*cnt*/, int /*to*/); + +int epoll_create(int); +int epoll_create1(int); +int epoll_ctl(int, int, int, struct epoll_event *); +int epoll_wait(int, struct epoll_event *, int, int); #if 0 int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); #endif From ea515f060411bec7abfa6c52eeb50f62071fd16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 4 Nov 2019 01:29:15 +0100 Subject: [PATCH 67/75] add support for one poll-only fd per epoll instance --- src/epoll.c | 14 ++++++-- src/epollfd_ctx.c | 82 ++++++++++++++++++++++++++++++++++++----------- src/epollfd_ctx.h | 5 +++ test/epoll-test.c | 10 +++++- 4 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/epoll.c b/src/epoll.c index 14991a1..1ef78d1 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -176,8 +177,17 @@ epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, assert(to != 0); - struct pollfd pfd = {.fd = epollfd->kq, .events = POLLIN}; - if (poll(&pfd, 1, to) < 0) { + /* + * We should add a notification mechanism when a new poll-only + * fd gets registered when this thread sleeps... + */ + struct pollfd pfds[2]; + (void)pthread_mutex_lock(&epollfd->mutex); + pfds[0] = epollfd->pfds[0]; + pfds[1] = epollfd->pfds[1]; + (void)pthread_mutex_unlock(&epollfd->mutex); + + if (poll(pfds, 2, MAX(to, -1)) < 0) { return errno; } } diff --git a/src/epollfd_ctx.c b/src/epollfd_ctx.c index 3a00202..42547ed 100644 --- a/src/epollfd_ctx.c +++ b/src/epollfd_ctx.c @@ -28,6 +28,9 @@ epollfd_ctx_init(EpollFDCtx *epollfd, int kq) .registered_fds = RB_INITIALIZER(®istered_fds), }; + epollfd->pfds[0].fd = -1; + epollfd->pfds[1] = (struct pollfd){.fd = kq, .events = POLLIN}; + if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { return ec; } @@ -225,12 +228,16 @@ epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, #undef SET_FLAG } else if (op == EPOLL_CTL_DEL) { - /* Removed poll fd here. */ - if (!(flags & KQUEUE_STATE_REGISTERED)) { return ENOENT; } + if (fd2 >= 0 && fd2 == epollfd->pfds[0].fd) { + epollfd->pfds[0].fd = -1; + kqueue_save_state(epollfd->kq, (uint32_t)fd2, 0); + return 0; + } + EV_SET(&kev[0], fd2, EVFILT_READ, EV_DELETE, 0, 0, 0); EV_SET(&kev[1], fd2, EVFILT_WRITE, EV_DELETE, 0, 0, 0); @@ -284,8 +291,18 @@ epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, return EINVAL; } - /* Checked for 'kev[i].data == ENODEV' here (for fds that only - * support poll). */ + /* Check for fds that only support poll. */ + if (kev[i].data == ENODEV && fd2 >= 0 && + !(ev->events & ~(uint32_t)(EPOLLIN | EPOLLOUT)) && + (epollfd->pfds[0].fd < 0 || epollfd->pfds[0].fd == fd2)) { + epollfd->pfds[0] = (struct pollfd){ + .fd = fd2, + .events = ((ev->events & EPOLLIN) ? POLLIN : 0) | + ((ev->events & EPOLLOUT) ? POLLOUT : 0), + }; + epollfd->pollfd_data = ev->data; + goto out; + } /* * Ignore EVFILT_WRITE registration EINVAL errors (some fd @@ -302,8 +319,8 @@ epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, if (kev[i].data != 0) { if (i == 0 && (kev[i].data == ENOENT || kev[i].data == EBADF)) { - kqueue_save_state(epollfd->kq, (uint32_t)fd2, - 0); + kqueue_save_state(epollfd->kq, /**/ + (uint32_t)fd2, 0); } return (int)kev[i].data; } @@ -337,6 +354,7 @@ epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, } } +out: if ((ec = kqueue_save_state(epollfd->kq, (uint32_t)fd2, flags)) != 0) { return ec; } @@ -359,6 +377,32 @@ epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) return ec; } +#define SUPPORTED_POLLFLAGS (POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL) + +static uint32_t +poll_to_epoll(int flags) +{ + uint32_t epoll_flags = 0; + + if (flags & POLLIN) { + epoll_flags |= EPOLLIN; + } + if (flags & POLLOUT) { + epoll_flags |= EPOLLOUT; + } + if (flags & POLLERR) { + epoll_flags |= EPOLLERR; + } + if (flags & POLLHUP) { + epoll_flags |= EPOLLHUP; + } + if (flags & POLLNVAL) { + epoll_flags |= EPOLLNVAL; + } + + return epoll_flags; +} + static errno_t epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt) @@ -367,25 +411,25 @@ epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, return EINVAL; } - /* Top level poll call was here (to support registered fd's that only - * support poll). */ - -#ifdef notyet - struct timespec timeout = {0, 0}; - if (to > 0) { - timeout.tv_sec = to / 1000; - timeout.tv_nsec = (to % 1000) * 1000 * 1000; + int ret = poll(epollfd->pfds, 2, 0); + if (ret < 0) { + return errno; + } + if (ret == 0) { + *actual_cnt = 0; + return 0; } - struct timespec *ptimeout = NULL; - if (to >= 0) { - ptimeout = &timeout; + if (epollfd->pfds[0].revents & SUPPORTED_POLLFLAGS) { + ev[0].events = poll_to_epoll(epollfd->pfds[0].revents); + ev[0].data = epollfd->pollfd_data; + *actual_cnt = 1; + return 0; } -#endif again:; struct kevent evlist[32]; - int ret = kevent(epollfd->kq, NULL, 0, evlist, cnt, + ret = kevent(epollfd->kq, NULL, 0, evlist, cnt, &(struct timespec){0, 0}); if (ret < 0) { return errno; diff --git a/src/epollfd_ctx.h b/src/epollfd_ctx.h index d2c427a..fec09bd 100644 --- a/src/epollfd_ctx.h +++ b/src/epollfd_ctx.h @@ -9,6 +9,7 @@ #include #include +#include #include struct registered_fds_node_; @@ -25,6 +26,10 @@ typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet; typedef struct { int kq; // non owning pthread_mutex_t mutex; + + struct pollfd pfds[2]; + epoll_data_t pollfd_data; + RegisteredFDsSet registered_fds; } EpollFDCtx; diff --git a/test/epoll-test.c b/test/epoll-test.c index 979a28b..05ceab0 100644 --- a/test/epoll-test.c +++ b/test/epoll-test.c @@ -521,7 +521,11 @@ test11() } struct epoll_event event_result; - if (epoll_wait(ep, &event_result, 1, 300) != 0) { + if (epoll_wait(ep, &event_result, 1, 300) != 1) { + return -1; + } + + if (event_result.events != EPOLLOUT || event_result.data.fd != fd) { return -1; } @@ -529,6 +533,10 @@ test11() return -1; } + if (epoll_ctl(ep, EPOLL_CTL_DEL, fd, NULL) >= 0 || errno != ENOENT) { + return -1; + } + close(fd); out: close(ep); From b99d614abf0c58b43bc5cf796c38780aeb71d5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 9 Nov 2019 14:11:27 +0100 Subject: [PATCH 68/75] fix build under FreeBSD 11.3 --- src/epoll.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/epoll.c b/src/epoll.c index 1ef78d1..bd5a8be 100644 --- a/src/epoll.c +++ b/src/epoll.c @@ -12,6 +12,20 @@ #include "epoll_shim_ctx.h" +// TODO(jan): Remove this once the definition is exposed in in +// all supported FreeBSD versions. +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + static errno_t epollfd_close(FDContextMapNode *node) { From 354324a464944825d8f07f32d60c1f44b6459ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 9 Nov 2019 22:50:24 +0100 Subject: [PATCH 69/75] try to make CI work again --- .cirrus.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 20fe346..6bcc24b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -11,5 +11,13 @@ task: - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf - pkg upgrade -y - script: | - make -j 4 + build_script: + - pwd + - mkdir build + - cd build + - cmake .. -DCMAKE_BUILD_TYPE=Release + - cmake --build . -- -j4 + test_script: + - pwd + - cd build + - ctest From a449a25c9f824ea5bfd0b8940cb0748d841c5e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 9 Nov 2019 23:16:25 +0100 Subject: [PATCH 70/75] CI: install cmake --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 6bcc24b..49d2b8e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -10,7 +10,7 @@ task: install_script: - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf - pkg upgrade -y - + - pkg install -y cmake build_script: - pwd - mkdir build From 2575d0aff69fb9525e06fbcc2af3a2a9b9a7b7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 9 Nov 2019 23:56:41 +0100 Subject: [PATCH 71/75] CI: increase verbosity of test results --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 49d2b8e..cb865f2 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -20,4 +20,4 @@ task: test_script: - pwd - cd build - - ctest + - ctest -VV --output-on-failure From 160581d07ac60c381fd292b6cc9555cd999012df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 10 Nov 2019 00:20:22 +0100 Subject: [PATCH 72/75] test/many-timers: allow some additional timer slack --- test/many-timers.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/many-timers.c b/test/many-timers.c index eba62d8..44e4c3b 100644 --- a/test/many-timers.c +++ b/test/many-timers.c @@ -208,8 +208,10 @@ main() REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); timespecsub(&e, &b, &e); + fprintf(stderr, "line %d: %ld/%ld\n", __LINE__, /**/ + (long)e.tv_sec, (long)e.tv_nsec); REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 150000000 && - e.tv_nsec < 200000000); + e.tv_nsec < 400000000); #ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); @@ -250,8 +252,11 @@ main() REQUIRE(clock_gettime(CLOCK_MONOTONIC, &e) == 0); timespecsub(&e, &b, &e); + + fprintf(stderr, "line %d: %ld/%ld\n", __LINE__, /**/ + (long)e.tv_sec, (long)e.tv_nsec); REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 300000000 && - e.tv_nsec < 350000000); + e.tv_nsec < 550000000); time = (struct itimerspec){ .it_value.tv_sec = 1, From 283f48b10960fb422d53907595620b843ee62ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 10 Nov 2019 01:03:50 +0100 Subject: [PATCH 73/75] test/many-timers: revert timer slack change --- test/many-timers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/many-timers.c b/test/many-timers.c index 44e4c3b..eb6e16e 100644 --- a/test/many-timers.c +++ b/test/many-timers.c @@ -211,7 +211,7 @@ main() fprintf(stderr, "line %d: %ld/%ld\n", __LINE__, /**/ (long)e.tv_sec, (long)e.tv_nsec); REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 150000000 && - e.tv_nsec < 400000000); + e.tv_nsec < 200000000); #ifndef __linux__ REQUIRE(is_fast_timer(timerfd)); @@ -256,7 +256,7 @@ main() fprintf(stderr, "line %d: %ld/%ld\n", __LINE__, /**/ (long)e.tv_sec, (long)e.tv_nsec); REQUIRE(e.tv_sec == 0 && e.tv_nsec >= 300000000 && - e.tv_nsec < 550000000); + e.tv_nsec < 350000000); time = (struct itimerspec){ .it_value.tv_sec = 1, From bd90ddbbbd2e01c771fe122f2aca89184cd3a35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 10 Nov 2019 01:05:44 +0100 Subject: [PATCH 74/75] CI: update to FreeBSD 11.3 On FreeBSD 11.2, the many-timers test fails because of: https://svnweb.freebsd.org/base?view=revision&revision=336761 Since FreeBSD 11.2 is not longer supported, I guess it's OK to update to 11.3. This uses the image_family keys as described here: https://github.com/cirruslabs/cirrus-ci-docs/blob/master/docs/guide/FreeBSD.md For FreeBSD 11.3, one has to specify "freebsd-11-3-snap" since "freebsd-11-3" does not boot. --- .cirrus.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index cb865f2..6d2f10f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,8 +5,8 @@ env: task: freebsd_instance: matrix: - image: freebsd-12-0-release-amd64 - image: freebsd-11-2-release-amd64 + image-family: freebsd-12-0 + image-family: freebsd-11-3-snap install_script: - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf - pkg upgrade -y From 7d68a8c72bc13e3cb0d9e2bd0d63bef43ace953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 10 Nov 2019 01:11:00 +0100 Subject: [PATCH 75/75] CI: try to use FreeBSD 11.3 in another way see: https://github.com/cirruslabs/cirrus-ci-docs/commit/1287ca6e6f289ee846dea237376e1da2496d8eee --- .cirrus.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 6d2f10f..375ec01 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,8 +5,8 @@ env: task: freebsd_instance: matrix: - image-family: freebsd-12-0 - image-family: freebsd-11-3-snap + image: freebsd-12-0-release-amd64 + image: freebsd-11-3-stable-amd64-v20190808 install_script: - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf - pkg upgrade -y