Commit c50dd602 authored by Kenton Varda's avatar Kenton Varda

Use edge-triggered epoll on Linux.

parent 50b9a529
......@@ -110,8 +110,7 @@ class AsyncStreamFd: public OwnedFileDescriptor, public AsyncIoStream {
public:
AsyncStreamFd(UnixEventPort& eventPort, int fd, uint flags)
: OwnedFileDescriptor(fd, flags),
readObserver(eventPort, fd),
writeObserver(eventPort, fd) {}
observer(eventPort, fd, UnixEventPort::FdObserver::OBSERVE_READ_WRITE) {}
virtual ~AsyncStreamFd() noexcept(false) {}
Promise<size_t> read(void* buffer, size_t minBytes, size_t maxBytes) override {
......@@ -157,7 +156,7 @@ public:
buffer = reinterpret_cast<const byte*>(buffer) + n;
size -= n;
return writeObserver.whenBecomesWritable().then([=]() {
return observer.whenBecomesWritable().then([=]() {
return write(buffer, size);
});
}
......@@ -192,7 +191,7 @@ public:
if (pollResult == 0) {
// Not ready yet. We can safely use the edge-triggered observer.
return readObserver.whenBecomesReadable();
return observer.whenBecomesReadable();
} else {
// Ready now.
return kj::READY_NOW;
......@@ -200,8 +199,7 @@ public:
}
private:
UnixEventPort::ReadObserver readObserver;
UnixEventPort::WriteObserver writeObserver;
UnixEventPort::FdObserver observer;
Promise<size_t> tryReadInternal(void* buffer, size_t minBytes, size_t maxBytes,
size_t alreadyRead) {
......@@ -226,7 +224,7 @@ private:
if (n < 0) {
// Read would block.
return readObserver.whenBecomesReadable().then([=]() {
return observer.whenBecomesReadable().then([=]() {
return tryReadInternal(buffer, minBytes, maxBytes, alreadyRead);
});
} else if (n == 0) {
......@@ -243,7 +241,7 @@ private:
maxBytes -= n;
alreadyRead += n;
KJ_IF_MAYBE(atEnd, readObserver.atEndHint()) {
KJ_IF_MAYBE(atEnd, observer.atEndHint()) {
if (*atEnd) {
// We've already received an indication that the next read() will return EOF, so there's
// nothing to wait for.
......@@ -256,7 +254,7 @@ private:
// that even if it was received since then, whenBecomesReadable() will catch that. So,
// let's go ahead and skip calling read() here and instead go straight to waiting for
// more input.
return readObserver.whenBecomesReadable().then([=]() {
return observer.whenBecomesReadable().then([=]() {
return tryReadInternal(buffer, minBytes, maxBytes, alreadyRead);
});
}
......@@ -304,7 +302,7 @@ private:
if (n < firstPiece.size()) {
// Only part of the first piece was consumed. Wait for buffer space and then write again.
firstPiece = firstPiece.slice(n, firstPiece.size());
return writeObserver.whenBecomesWritable().then([=]() {
return observer.whenBecomesWritable().then([=]() {
return writeInternal(firstPiece, morePieces);
});
} else if (morePieces.size() == 0) {
......@@ -731,7 +729,8 @@ Promise<Array<SocketAddress>> SocketAddress::lookupHost(
class FdConnectionReceiver final: public ConnectionReceiver, public OwnedFileDescriptor {
public:
FdConnectionReceiver(UnixEventPort& eventPort, int fd, uint flags)
: OwnedFileDescriptor(fd, flags), eventPort(eventPort), observer(eventPort, fd) {}
: OwnedFileDescriptor(fd, flags), eventPort(eventPort),
observer(eventPort, fd, UnixEventPort::FdObserver::OBSERVE_READ) {}
Promise<Own<AsyncIoStream>> accept() override {
int newFd;
......@@ -785,7 +784,7 @@ public:
public:
UnixEventPort& eventPort;
UnixEventPort::ReadObserver observer;
UnixEventPort::FdObserver observer;
};
class TimerImpl final: public Timer {
......
......@@ -86,6 +86,27 @@ TEST_F(AsyncUnixTest, SignalWithValue) {
EXPECT_SI_CODE(SI_QUEUE, info.si_code);
EXPECT_EQ(123, info.si_value.sival_int);
}
TEST_F(AsyncUnixTest, SignalWithPointerValue) {
// This tests that if we use sigqueue() to attach a value to the signal, that value is received
// correctly. Note that this only works on platforms that support real-time signals -- even
// though the signal we're sending is SIGURG, the sigqueue() system call is introduced by RT
// signals. Hence this test won't run on e.g. Mac OSX.
UnixEventPort port;
EventLoop loop(port);
WaitScope waitScope(loop);
union sigval value;
memset(&value, 0, sizeof(value));
value.sival_ptr = &port;
sigqueue(getpid(), SIGURG, value);
siginfo_t info = port.onSignal(SIGURG).wait(waitScope);
EXPECT_EQ(SIGURG, info.si_signo);
EXPECT_SI_CODE(SI_QUEUE, info.si_code);
EXPECT_EQ(&port, info.si_value.sival_ptr);
}
#endif
TEST_F(AsyncUnixTest, SignalsMultiListen) {
......@@ -207,14 +228,14 @@ TEST_F(AsyncUnixTest, ReadObserver) {
KJ_SYSCALL(pipe(pipefds));
kj::AutoCloseFd infd(pipefds[0]), outfd(pipefds[1]);
UnixEventPort::ReadObserver readObserver(port, infd);
UnixEventPort::FdObserver observer(port, infd, UnixEventPort::FdObserver::OBSERVE_READ);
KJ_SYSCALL(write(outfd, "foo", 3));
readObserver.whenBecomesReadable().wait(waitScope);
observer.whenBecomesReadable().wait(waitScope);
#if __linux__ // platform known to support POLLRDHUP
EXPECT_FALSE(KJ_ASSERT_NONNULL(readObserver.atEndHint()));
EXPECT_FALSE(KJ_ASSERT_NONNULL(observer.atEndHint()));
char buffer[4096];
ssize_t n;
......@@ -224,9 +245,9 @@ TEST_F(AsyncUnixTest, ReadObserver) {
KJ_SYSCALL(write(outfd, "bar", 3));
outfd = nullptr;
readObserver.whenBecomesReadable().wait(waitScope);
observer.whenBecomesReadable().wait(waitScope);
EXPECT_TRUE(KJ_ASSERT_NONNULL(readObserver.atEndHint()));
EXPECT_TRUE(KJ_ASSERT_NONNULL(observer.atEndHint()));
#endif
}
......@@ -237,10 +258,12 @@ TEST_F(AsyncUnixTest, ReadObserverMultiListen) {
int bogusPipefds[2];
KJ_SYSCALL(pipe(bogusPipefds));
UnixEventPort::ReadObserver bogusReadObserver(port, bogusPipefds[0]);
KJ_DEFER({ close(bogusPipefds[1]); close(bogusPipefds[0]); });
bogusReadObserver.whenBecomesReadable().then([]() {
UnixEventPort::FdObserver bogusObserver(port, bogusPipefds[0],
UnixEventPort::FdObserver::OBSERVE_READ);
bogusObserver.whenBecomesReadable().then([]() {
ADD_FAILURE() << "Received wrong poll.";
}).detach([](kj::Exception&& exception) {
ADD_FAILURE() << kj::str(exception).cStr();
......@@ -250,10 +273,11 @@ TEST_F(AsyncUnixTest, ReadObserverMultiListen) {
KJ_SYSCALL(pipe(pipefds));
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
UnixEventPort::ReadObserver readObserver(port, pipefds[0]);
UnixEventPort::FdObserver observer(port, pipefds[0],
UnixEventPort::FdObserver::OBSERVE_READ);
KJ_SYSCALL(write(pipefds[1], "foo", 3));
readObserver.whenBecomesReadable().wait(waitScope);
observer.whenBecomesReadable().wait(waitScope);
}
TEST_F(AsyncUnixTest, ReadObserverMultiReceive) {
......@@ -265,18 +289,22 @@ TEST_F(AsyncUnixTest, ReadObserverMultiReceive) {
KJ_SYSCALL(pipe(pipefds));
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
UnixEventPort::ReadObserver readObserver(port, pipefds[0]);
UnixEventPort::FdObserver observer(port, pipefds[0],
UnixEventPort::FdObserver::OBSERVE_READ);
KJ_SYSCALL(write(pipefds[1], "foo", 3));
int pipefds2[2];
KJ_SYSCALL(pipe(pipefds2));
KJ_DEFER({ close(pipefds2[1]); close(pipefds2[0]); });
UnixEventPort::ReadObserver readObserver2(port, pipefds2[0]);
UnixEventPort::FdObserver observer2(port, pipefds2[0],
UnixEventPort::FdObserver::OBSERVE_READ);
KJ_SYSCALL(write(pipefds2[1], "bar", 3));
readObserver.whenBecomesReadable().wait(waitScope);
readObserver2.whenBecomesReadable().wait(waitScope);
auto promise1 = observer.whenBecomesReadable();
auto promise2 = observer2.whenBecomesReadable();
promise1.wait(waitScope);
promise2.wait(waitScope);
}
TEST_F(AsyncUnixTest, ReadObserverAsync) {
......@@ -288,7 +316,8 @@ TEST_F(AsyncUnixTest, ReadObserverAsync) {
int pipefds[2];
KJ_SYSCALL(pipe(pipefds));
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
UnixEventPort::ReadObserver readObserver(port, pipefds[0]);
UnixEventPort::FdObserver observer(port, pipefds[0],
UnixEventPort::FdObserver::OBSERVE_READ);
Thread thread([&]() {
delay();
......@@ -296,7 +325,7 @@ TEST_F(AsyncUnixTest, ReadObserverAsync) {
});
// Wait for the event in this thread.
readObserver.whenBecomesReadable().wait(waitScope);
observer.whenBecomesReadable().wait(waitScope);
}
TEST_F(AsyncUnixTest, ReadObserverNoWait) {
......@@ -309,18 +338,20 @@ TEST_F(AsyncUnixTest, ReadObserverNoWait) {
int pipefds[2];
KJ_SYSCALL(pipe(pipefds));
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
UnixEventPort::ReadObserver readObserver(port, pipefds[0]);
UnixEventPort::FdObserver observer(port, pipefds[0],
UnixEventPort::FdObserver::OBSERVE_READ);
int pipefds2[2];
KJ_SYSCALL(pipe(pipefds2));
KJ_DEFER({ close(pipefds2[1]); close(pipefds2[0]); });
UnixEventPort::ReadObserver readObserver2(port, pipefds2[0]);
UnixEventPort::FdObserver observer2(port, pipefds2[0],
UnixEventPort::FdObserver::OBSERVE_READ);
int receivedCount = 0;
readObserver.whenBecomesReadable().then([&]() {
observer.whenBecomesReadable().then([&]() {
receivedCount++;
}).detach([](Exception&& e) { ADD_FAILURE() << str(e).cStr(); });
readObserver2.whenBecomesReadable().then([&]() {
observer2.whenBecomesReadable().then([&]() {
receivedCount++;
}).detach([](Exception&& e) { ADD_FAILURE() << str(e).cStr(); });
......@@ -360,7 +391,7 @@ TEST_F(AsyncUnixTest, WriteObserver) {
kj::AutoCloseFd infd(pipefds[0]), outfd(pipefds[1]);
setNonblocking(outfd);
UnixEventPort::WriteObserver writeObserver(port, outfd);
UnixEventPort::FdObserver observer(port, outfd, UnixEventPort::FdObserver::OBSERVE_WRITE);
// Fill buffer.
ssize_t n;
......@@ -369,7 +400,7 @@ TEST_F(AsyncUnixTest, WriteObserver) {
} while (n >= 0);
bool writable = false;
auto promise = writeObserver.whenBecomesWritable()
auto promise = observer.whenBecomesWritable()
.then([&]() { writable = true; }).eagerlyEvaluate(nullptr);
loop.run();
......
This diff is collapsed.
......@@ -29,8 +29,12 @@
#include "async.h"
#include "time.h"
#include "vector.h"
#include "io.h"
#include <signal.h>
#include <pthread.h>
#if __linux__ && !defined(KJ_USE_EPOLL)
#define KJ_USE_EPOLL 1
#endif
namespace kj {
......@@ -51,9 +55,8 @@ public:
UnixEventPort();
~UnixEventPort() noexcept(false);
class ReadObserver;
class WriteObserver;
// Classes that watch an fd for readability or writability. See definitions below.
class FdObserver;
// Class that watches an fd for readability or writability. See definition below.
Promise<siginfo_t> onSignal(int signum);
// When the given signal is delivered to this thread, return the corresponding siginfo_t.
......@@ -91,34 +94,43 @@ public:
void poll() override;
private:
#if KJ_USE_EPOLL
// TODO(soon): epoll implementation
#else
class PollPromiseAdapter;
class SignalPromiseAdapter;
class TimerPromiseAdapter;
class PollContext;
struct TimerSet; // Defined in source file to avoid STL include.
class TimerPromiseAdapter;
class SignalPromiseAdapter;
PollPromiseAdapter* pollHead = nullptr;
PollPromiseAdapter** pollTail = &pollHead;
SignalPromiseAdapter* signalHead = nullptr;
SignalPromiseAdapter** signalTail = &signalHead;
Own<TimerSet> timers;
TimePoint frozenSteadyTime;
void gotSignal(const siginfo_t& siginfo);
SignalPromiseAdapter* signalHead = nullptr;
SignalPromiseAdapter** signalTail = &signalHead;
TimePoint currentSteadyTime();
void processTimers();
void gotSignal(const siginfo_t& siginfo);
friend class TimerPromiseAdapter;
#if KJ_USE_EPOLL
AutoCloseFd epollFd;
AutoCloseFd signalFd;
AutoCloseFd eventFd; // Used for cross-thread wakeups.
sigset_t signalFdSigset;
// Signal mask as currently set on the signalFd. Tracked so we can detect whether or not it
// needs updating.
void doEpollWait(int timeout);
#else
class PollContext;
FdObserver* observersHead = nullptr;
FdObserver** observersTail = &observersHead;
#endif
};
class UnixEventPort::ReadObserver {
// Object which watches a file descriptor to determine when it is readable.
class UnixEventPort::FdObserver {
// Object which watches a file descriptor to determine when it is readable or writable.
//
// For listen sockets, "readable" means that there is a connection to accept(). For everything
// else, it means that read() (or recv()) will return data.
......@@ -133,11 +145,19 @@ class UnixEventPort::ReadObserver {
// behavior. If at all possible, use the higher-level AsyncInputStream interface instead.
public:
ReadObserver(UnixEventPort& eventPort, int fd): eventPort(eventPort), fd(fd) {}
enum Flags {
OBSERVE_READ = 1,
OBSERVE_WRITE = 2,
OBSERVE_READ_WRITE = OBSERVE_READ | OBSERVE_WRITE
};
FdObserver(UnixEventPort& eventPort, int fd, uint flags);
// Begin watching the given file descriptor for readability. Only one ReadObserver may exist
// for a given file descriptor at a time.
KJ_DISALLOW_COPY(ReadObserver);
~FdObserver() noexcept(false);
KJ_DISALLOW_COPY(FdObserver);
Promise<void> whenBecomesReadable();
// Resolves the next time the file descriptor transitions from having no data to read to having
......@@ -177,37 +197,6 @@ public:
//
// This hint may be useful as an optimization to avoid an unnecessary system call.
private:
UnixEventPort& eventPort;
int fd;
#if KJ_USE_EPOLL
// TODO(soon): epoll implementation
Own<PromiseFulfiller<void>> fulfiller;
// Replaced each time `whenBecomesReadable()` is called.
#else
#endif
Maybe<bool> atEnd;
friend class UnixEventPort;
};
class UnixEventPort::WriteObserver {
// Object which watches a file descriptor to determine when it is writable.
//
// WARNING: The exact behavior of this class differs across systems, since event interfaces
// vary wildly. Be sure to read the documentation carefully and avoid depending on unspecified
// behavior. If at all possible, use the higher-level AsyncOutputStream interface instead.
public:
WriteObserver(UnixEventPort& eventPort, int fd): eventPort(eventPort), fd(fd) {}
// Begin watching the given file descriptor for writability. Only one WriteObserver may exist
// for a given file descriptor at a time.
KJ_DISALLOW_COPY(WriteObserver);
Promise<void> whenBecomesWritable();
// Resolves the next time the file descriptor transitions from having no space available in the
// write buffer to having some space available.
......@@ -232,11 +221,24 @@ public:
private:
UnixEventPort& eventPort;
int fd;
uint flags;
#if KJ_USE_EPOLL
Own<PromiseFulfiller<void>> fulfiller;
// Replaced each time `whenBecomesReadable()` is called.
#else
kj::Maybe<Own<PromiseFulfiller<void>>> readFulfiller;
kj::Maybe<Own<PromiseFulfiller<void>>> writeFulfiller;
// Replaced each time `whenBecomesReadable()` or `whenBecomesWritable()` is called. Reverted to
// null every time an event is fired.
Maybe<bool> atEnd;
void fire(short events);
#if !KJ_USE_EPOLL
FdObserver* next;
FdObserver** prev;
// Linked list of observers which currently have a non-null readFulfiller or writeFulfiller.
// If `prev` is null then the observer is not currently in the list.
short getEventMask();
#endif
friend class UnixEventPort;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment