Commit a0e2a45f authored by Kenton Varda's avatar Kenton Varda

UnixEventLoop supports polling fds and catching signals.

parent e7d27780
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. 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.
//
// 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 OWNER 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.
#include "async-unix.h"
#include "thread.h"
#include "debug.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <gtest/gtest.h>
namespace kj {
inline void delay() { usleep(10000); }
class DummyErrorHandler: public TaskSet::ErrorHandler {
public:
void taskFailed(kj::Exception&& exception) override {
kj::throwRecoverableException(kj::mv(exception));
}
};
class AsyncUnixTest: public testing::Test {
public:
static void SetUpTestCase() {
UnixEventLoop::captureSignal(SIGUSR2);
UnixEventLoop::captureSignal(SIGIO);
}
};
TEST_F(AsyncUnixTest, Signals) {
UnixEventLoop loop;
union sigval value;
value.sival_int = 123;
sigqueue(getpid(), SIGUSR2, value);
siginfo_t info = loop.wait(loop.onSignal(SIGUSR2));
EXPECT_EQ(SIGUSR2, info.si_signo);
EXPECT_EQ(SI_QUEUE, info.si_code);
EXPECT_EQ(123, info.si_value.sival_int);
}
TEST_F(AsyncUnixTest, SignalsMulti) {
UnixEventLoop loop;
DummyErrorHandler dummyHandler;
TaskSet tasks(loop, dummyHandler);
tasks.add(loop.onSignal(SIGIO).thenInAnyThread([](siginfo_t&&) {
ADD_FAILURE() << "Received wrong signal.";
}));
union sigval value;
value.sival_int = 123;
sigqueue(getpid(), SIGUSR2, value);
siginfo_t info = loop.wait(loop.onSignal(SIGUSR2));
EXPECT_EQ(SIGUSR2, info.si_signo);
EXPECT_EQ(SI_QUEUE, info.si_code);
EXPECT_EQ(123, info.si_value.sival_int);
}
TEST_F(AsyncUnixTest, SignalsAsync) {
// Arrange for another thread to wait on a UnixEventLoop...
auto exitThread = newPromiseAndFulfiller<void>();
UnixEventLoop unixLoop;
Thread thread([&]() {
unixLoop.wait(kj::mv(exitThread.promise));
});
KJ_DEFER(exitThread.fulfiller->fulfill());
// Arrange to catch a signal in the other thread. But we haven't sent one yet.
bool received = false;
Promise<void> promise = unixLoop.there(unixLoop.onSignal(SIGUSR2),
[&](siginfo_t&& info) {
received = true;
EXPECT_EQ(SIGUSR2, info.si_signo);
EXPECT_EQ(SI_QUEUE, info.si_code);
EXPECT_EQ(123, info.si_value.sival_int);
});
delay();
EXPECT_FALSE(received);
union sigval value;
value.sival_int = 123;
sigqueue(getpid(), SIGUSR2, value);
SimpleEventLoop mainLoop;
mainLoop.wait(kj::mv(promise));
EXPECT_TRUE(received);
}
TEST_F(AsyncUnixTest, Poll) {
UnixEventLoop loop;
int pipefds[2];
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
KJ_SYSCALL(pipe(pipefds));
KJ_SYSCALL(write(pipefds[1], "foo", 3));
EXPECT_EQ(POLLIN, loop.wait(loop.onFdEvent(pipefds[0], POLLIN | POLLPRI)));
}
TEST_F(AsyncUnixTest, PollMulti) {
UnixEventLoop loop;
DummyErrorHandler dummyHandler;
int bogusPipefds[2];
KJ_SYSCALL(pipe(bogusPipefds));
KJ_DEFER({ close(bogusPipefds[1]); close(bogusPipefds[0]); });
TaskSet tasks(loop, dummyHandler);
tasks.add(loop.onFdEvent(bogusPipefds[0], POLLIN | POLLPRI).thenInAnyThread([](short s) {
KJ_DBG(s);
ADD_FAILURE() << "Received wrong poll.";
}));
int pipefds[2];
KJ_SYSCALL(pipe(pipefds));
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
KJ_SYSCALL(write(pipefds[1], "foo", 3));
EXPECT_EQ(POLLIN, loop.wait(loop.onFdEvent(pipefds[0], POLLIN | POLLPRI)));
}
TEST_F(AsyncUnixTest, PollAsync) {
// Arrange for another thread to wait on a UnixEventLoop...
auto exitThread = newPromiseAndFulfiller<void>();
UnixEventLoop unixLoop;
Thread thread([&]() {
unixLoop.wait(kj::mv(exitThread.promise));
});
KJ_DEFER(exitThread.fulfiller->fulfill());
// Make a pipe and wait on its read end in another thread. But don't write to it yet.
int pipefds[2];
KJ_DEFER({ close(pipefds[1]); close(pipefds[0]); });
KJ_SYSCALL(pipe(pipefds));
bool received = false;
Promise<void> promise = unixLoop.there(unixLoop.onFdEvent(pipefds[0], POLLIN | POLLPRI),
[&](short events) {
received = true;
EXPECT_EQ(POLLIN, events);
});
delay();
EXPECT_FALSE(received);
KJ_SYSCALL(write(pipefds[1], "foo", 3));
SimpleEventLoop mainLoop;
mainLoop.wait(kj::mv(promise));
EXPECT_TRUE(received);
}
} // namespace kj
This diff is collapsed.
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. 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.
//
// 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 OWNER 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.
#ifndef KJ_ASYNC_UNIX_H_
#define KJ_ASYNC_UNIX_H_
#include "async.h"
#include "vector.h"
#include <signal.h>
#include <poll.h>
#include <pthread.h>
namespace kj {
class UnixEventLoop: public EventLoop {
// An EventLoop implementation which can wait for events on file descriptors as well as signals.
// This API only makes sense on Unix.
//
// The implementation uses `poll()` or possibly a platform-specific API (e.g. epoll, kqueue).
// To also wait on signals without race conditions, the implementation may block signals until
// just before `poll()` while using a signal handler which `siglongjmp()`s back to just before
// the signal was unblocked, or it may use a nicer platform-specific API like signalfd.
//
// The implementation uses SIGUSR1. The application must avoid using this signal for its own
// purposes.
public:
UnixEventLoop();
~UnixEventLoop();
Promise<short> onFdEvent(int fd, short eventMask) const;
// `eventMask` is a bitwise-OR of poll events (e.g. `POLLIN`, `POLLOUT`, etc.). The next time
// one or more of the given events occurs on `fd`, the set of events that occurred are returned.
//
// The result of waiting on the same FD twice at once is undefined.
Promise<siginfo_t> onSignal(int signum) const;
// When the given signal is delivered to this thread, return the corresponding siginfo_t.
// The signal must have been captured using `captureSignal()`.
//
// If `onSignal()` has not been called, the signal will remain blocked in this thread.
// Therefore, a signal which arrives before `onSignal()` was called will not be "missed" -- the
// next call to 'onSignal()' will receive it. Also, you can control which thread receives a
// process-wide signal by only calling `onSignal()` on that thread's event loop.
//
// The result of waiting on the same signal twice at once is undefined.
static void captureSignal(int signum);
// Arranges for the given signal to be captured and handled via UnixEventLoop, so that you may
// then pass it to `onSignal()`. This method is static because it registers a signal handler
// which applies process-wide. If any other threads exist in the process when `captureSignal()`
// is called, you *must* set the signal mask in those threads to block this signal, otherwise
// terrible things will happen if the signal happens to be delivered to those threads. If at
// all possible, call `captureSignal()` *before* creating threads, so that threads you create in
// the future will inherit the proper signal mask.
//
// To un-capture a signal, simply install a different signal handler and then un-block it from
// the signal mask.
protected:
void prepareToSleep() noexcept override;
void sleep() override;
void wake() const override;
private:
class PollItem;
class PollPromiseAdapter;
class SignalItem;
class SignalPromiseAdapter;
struct Impl;
Own<Impl> impl;
pthread_t waitThread;
bool isSleeping = false;
};
} // namespace kj
#endif // KJ_ASYNC_UNIX_H_
...@@ -1481,8 +1481,8 @@ private: ...@@ -1481,8 +1481,8 @@ private:
template <typename T, typename Adapter, typename... Params> template <typename T, typename Adapter, typename... Params>
Promise<T> newAdaptedPromise(Params&&... adapterConstructorParams) { Promise<T> newAdaptedPromise(Params&&... adapterConstructorParams) {
return Promise<T>(Own<_::PromiseNode>(heap<_::AdapterPromiseNode<_::FixVoid<T>, Adapter>>( return Promise<T>(false, heap<_::AdapterPromiseNode<_::FixVoid<T>, Adapter>>(
kj::fwd<Params>(adapterConstructorParams)...))); kj::fwd<Params>(adapterConstructorParams)...));
} }
template <typename T> template <typename T>
......
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