Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .release-notes/5348.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Fix epoll backend losing signal counts on eventfd read failure

The epoll ASIO backend could deliver an uninitialized missed-signal count
to signal handlers when `read()` on the signal eventfd returned an error.
On read failure, the backend now waits for the next epoll iteration instead
of waking signal handlers with a misleading zero count, so handlers only run
with a real signal count.
1 change: 1 addition & 0 deletions packages/signals/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class \nodoc\ _TestSighupNotify is SignalNotify
_h = h

fun ref apply(count: U32): Bool =>
_h.assert_true(count >= 1)
_h.complete(true)
false

Expand Down
30 changes: 24 additions & 6 deletions src/libponyrt/asio/epoll.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ static void signal_handler(int sig)
eventfd_write(ev->fd, 1);
}

static bool read_signal_count(int fd, uint32_t* out_count)
{
uint64_t missed = 0;
ssize_t rc = read(fd, &missed, sizeof(uint64_t));

if(rc != sizeof(uint64_t))
return false;

*out_count = (uint32_t)missed;
return true;
}

bool ponyint_asio_epoll_read_signal_count(int fd, uint32_t* out_count)
{
return read_signal_count(fd, out_count);
}

#if !defined(USE_SCHEDULER_SCALING_PTHREADS)
static void empty_signal_handler(int sig)
{
Expand Down Expand Up @@ -288,9 +305,13 @@ DECLARE_THREAD_FN(ponyint_asio_backend_dispatch)
{
if(ep->events & (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR))
{
uint64_t missed;
uint64_t missed = 0;
ssize_t rc = read(ev->fd, &missed, sizeof(uint64_t));
/* missed is intentionally unused for timer events: the ASIO timer
* backend does not coalesce based on missed count today.
*/
(void)rc;
(void)missed;
flags |= ASIO_TIMER;
}
}
Expand All @@ -299,11 +320,8 @@ DECLARE_THREAD_FN(ponyint_asio_backend_dispatch)
{
if(ep->events & (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR))
{
uint64_t missed;
ssize_t rc = read(ev->fd, &missed, sizeof(uint64_t));
(void)rc;
flags |= ASIO_SIGNAL;
count = (uint32_t)missed;
if(read_signal_count(ev->fd, &count))
flags |= ASIO_SIGNAL;
}
}

Expand Down
1 change: 1 addition & 0 deletions test/libponyrt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ project(libponyrt.tests VERSION ${PONYC_PROJECT_VERSION} LANGUAGES C CXX)

add_executable(libponyrt.tests
util.cc
asio_epoll.cc
ds/fun.cc
ds/hash.cc
ds/list.cc
Expand Down
65 changes: 65 additions & 0 deletions test/libponyrt/asio_epoll.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <platform.h>

#include <gtest/gtest.h>

#include <stdint.h>

#if defined(PLATFORM_IS_LINUX)
#include <sys/eventfd.h>
#include <unistd.h>

extern "C" {
bool ponyint_asio_epoll_read_signal_count(int fd, uint32_t* out_count);
}

static void close_eventfd(int fd)
{
int rc = close(fd);
ASSERT_EQ(0, rc);
}

TEST(AsioEpoll, ReadSignalCountReturnsFalseForUnwrittenNonblockingEventfd)
{
int fd = eventfd(0, EFD_NONBLOCK);
ASSERT_NE(-1, fd);

uint32_t count = 123;
ASSERT_FALSE(ponyint_asio_epoll_read_signal_count(fd, &count));
ASSERT_EQ((uint32_t)123, count);

close_eventfd(fd);
}

TEST(AsioEpoll, ReadSignalCountReturnsWrittenValue)
{
int fd = eventfd(0, EFD_NONBLOCK);
ASSERT_NE(-1, fd);

ASSERT_EQ(0, eventfd_write(fd, 7));

uint32_t count = 0;
ASSERT_TRUE(ponyint_asio_epoll_read_signal_count(fd, &count));
ASSERT_EQ((uint32_t)7, count);

close_eventfd(fd);
}

TEST(AsioEpoll, ReadSignalCountAccumulatesWritesAndDrainsEventfd)
{
int fd = eventfd(0, EFD_NONBLOCK);
ASSERT_NE(-1, fd);

ASSERT_EQ(0, eventfd_write(fd, 3));
ASSERT_EQ(0, eventfd_write(fd, 4));

uint32_t count = 0;
ASSERT_TRUE(ponyint_asio_epoll_read_signal_count(fd, &count));
ASSERT_EQ((uint32_t)7, count);

count = 123;
ASSERT_FALSE(ponyint_asio_epoll_read_signal_count(fd, &count));
ASSERT_EQ((uint32_t)123, count);

close_eventfd(fd);
}
#endif
Loading