This was an absolute nightmare to diagnose. It turns out there's a kernel bug: poll with events = POLLHUP will receive an event for NOT POLLHUP internally in the kernel, delete their event subscription, and then not receive events for any HUP later. lol! lmao!! We choose to use plain old EVFILT_READ because the watched fd can be either a socket or a pipe and it's preferable to eat some spurious wakeups than have separate paths for those. The alternative is using EVFILT_SOCK, a private API that's existed for years and which netty uses for its sockets, but that doesn't work on pipes. Fixes: https://git.lix.systems/lix-project/lix/issues/729 Change-Id: If72b5d7a39f00320a9acccdbe81121cdb1a04c45
67 lines
1.6 KiB
C++
67 lines
1.6 KiB
C++
#include "lix/libutil/monitor-fd.hh"
|
|
#include "lix/libutil/error.hh"
|
|
#include "lix/libutil/file-descriptor.hh"
|
|
#include <atomic>
|
|
#include <future>
|
|
#include <gtest/gtest.h>
|
|
#include <sys/socket.h>
|
|
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
namespace nix {
|
|
|
|
TEST(MonitorFdHup, works)
|
|
{
|
|
int socks[2];
|
|
int rv = socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
|
|
if (rv) {
|
|
throw SysError("socketpair");
|
|
}
|
|
auto sock1 = AutoCloseFD{socks[0]};
|
|
auto sock2 = AutoCloseFD{socks[1]};
|
|
|
|
std::promise<void> called;
|
|
|
|
MonitorFdHup monitor(sock1.get(), [&called]() { called.set_value(); });
|
|
|
|
sock2.close();
|
|
|
|
// 10 seconds should certainly do it.
|
|
auto status = called.get_future().wait_for(10s);
|
|
ASSERT_EQ(status, std::future_status::ready);
|
|
}
|
|
|
|
// Ensures that it also works with pipes.
|
|
TEST(MonitorFdHup, works_with_pipes)
|
|
{
|
|
Pipe pipes{};
|
|
pipes.create();
|
|
std::promise<void> called;
|
|
|
|
MonitorFdHup monitor(pipes.readSide.get(), [&called]() { called.set_value(); });
|
|
|
|
pipes.writeSide.close();
|
|
|
|
// 10 seconds should certainly do it.
|
|
auto status = called.get_future().wait_for(10s);
|
|
ASSERT_EQ(status, std::future_status::ready);
|
|
}
|
|
|
|
// Ensures that destroying the MonitorFdHup causes it to actually go away.
|
|
TEST(MonitorFdHup, destroys_safely)
|
|
{
|
|
int socks[2];
|
|
int rv = socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
|
|
if (rv) {
|
|
throw SysError("socketpair");
|
|
}
|
|
auto sock1 = AutoCloseFD{socks[0]};
|
|
auto sock2 = AutoCloseFD{socks[1]};
|
|
|
|
{
|
|
MonitorFdHup monitor(sock1.get(), []() { abort(); });
|
|
}
|
|
}
|
|
|
|
}
|