Commit c0750662 authored by Kenton Varda's avatar Kenton Varda

Reduce generated code for Promises by making most of the node implementations mostly non-templated.

parent 6ff9769f
......@@ -40,7 +40,9 @@ namespace {
thread_local EventLoop* threadLocalEventLoop = nullptr;
class YieldPromiseNode final: public _::PromiseNode<_::Void>, public EventLoop::Event {
#define _kJ_ALREADY_READY reinterpret_cast< ::kj::EventLoop::Event*>(1)
class YieldPromiseNode final: public _::PromiseNode, public EventLoop::Event {
// A PromiseNode used to implement EventLoop::yield().
public:
......@@ -57,8 +59,8 @@ public:
return false;
}
}
_::ExceptionOr<_::Void> get() noexcept override {
return _::Void();
void get(_::ExceptionOrValue& output) noexcept override {
output.as<_::Void>() = _::Void();
}
Maybe<const EventLoop&> getSafeEventLoop() noexcept override {
return getEventLoop();
......@@ -74,6 +76,18 @@ private:
EventLoop::Event* onReadyEvent = nullptr;
};
class BoolEvent: public EventLoop::Event {
public:
BoolEvent(const EventLoop& loop): Event(loop) {}
~BoolEvent() { disarm(); }
bool fired = false;
void fire() override {
fired = true;
}
};
} // namespace
EventLoop& EventLoop::current() {
......@@ -91,12 +105,15 @@ EventLoop::EventLoop(): queue(*this) {
queue.prev = &queue;
}
void EventLoop::loopWhile(bool& keepGoing) {
void EventLoop::waitImpl(Own<_::PromiseNode> node, _::ExceptionOrValue& result) {
EventLoop* oldEventLoop = threadLocalEventLoop;
threadLocalEventLoop = this;
KJ_DEFER(threadLocalEventLoop = oldEventLoop);
while (keepGoing) {
BoolEvent event(*this);
event.fired = node->onReady(event);
while (!event.fired) {
queue.mutex.lock(_::Mutex::EXCLUSIVE);
// Get the first event in the queue.
......@@ -125,6 +142,9 @@ void EventLoop::loopWhile(bool& keepGoing) {
KJ_DEFER(event->mutex.unlock(_::Mutex::EXCLUSIVE));
event->fire();
}
KJ_DBG(&result);
node->get(result);
}
Promise<void> EventLoop::yield() {
......@@ -211,4 +231,195 @@ void SimpleEventLoop::wake() const {
}
}
// =======================================================================================
void PromiseBase::absolve() {
runCatchingExceptions([this]() { auto deleteMe = kj::mv(node); });
}
namespace _ { // private
bool PromiseNode::atomicOnReady(EventLoop::Event*& onReadyEvent, EventLoop::Event& newEvent) {
// If onReadyEvent is null, atomically set it to point at newEvent and return false.
// If onReadyEvent is _kJ_ALREADY_READY, return true.
// Useful for implementing onReady() thread-safely.
EventLoop::Event* oldEvent = nullptr;
if (__atomic_compare_exchange_n(&onReadyEvent, &oldEvent, &newEvent, false,
__ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
// Event was swapped in and will be called later.
return false;
} else {
// `onReadyEvent` is not null. If it is _kJ_ALREADY_READY then this promise was fulfilled
// before any dependent existed, otherwise there is already a different dependent.
KJ_IREQUIRE(oldEvent == _kJ_ALREADY_READY, "onReady() can only be called once.");
return true;
}
}
void PromiseNode::atomicReady(EventLoop::Event*& onReadyEvent) {
// If onReadyEvent is null, atomically set it to _kJ_ALREADY_READY.
// Otherwise, arm whatever it points at.
// Useful for firing events in conjuction with atomicOnReady().
EventLoop::Event* oldEvent = nullptr;
if (!__atomic_compare_exchange_n(&onReadyEvent, &oldEvent, _kJ_ALREADY_READY, false,
__ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
oldEvent->arm();
}
}
bool ImmediatePromiseNodeBase::onReady(EventLoop::Event& event) noexcept { return true; }
Maybe<const EventLoop&> ImmediatePromiseNodeBase::getSafeEventLoop() noexcept { return nullptr; }
ImmediateBrokenPromiseNode::ImmediateBrokenPromiseNode(Exception&& exception)
: exception(kj::mv(exception)) {}
void ImmediateBrokenPromiseNode::get(ExceptionOrValue& output) noexcept {
output.exception = kj::mv(exception);
}
TransformPromiseNodeBase::TransformPromiseNodeBase(
const EventLoop& loop, Own<PromiseNode>&& dependency)
: loop(loop), dependency(kj::mv(dependency)) {}
bool TransformPromiseNodeBase::onReady(EventLoop::Event& event) noexcept {
return dependency->onReady(event);
}
void TransformPromiseNodeBase::get(ExceptionOrValue& output) noexcept {
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
getImpl(output);
})) {
output.addException(kj::mv(*exception));
}
}
Maybe<const EventLoop&> TransformPromiseNodeBase::getSafeEventLoop() noexcept {
return loop;
}
ChainPromiseNode::ChainPromiseNode(const EventLoop& loop, Own<PromiseNode> inner)
: Event(loop), state(PRE_STEP1), inner(kj::mv(inner)) {
KJ_IREQUIRE(this->inner->isSafeEventLoop(loop));
arm();
}
ChainPromiseNode::~ChainPromiseNode() noexcept(false) {
disarm();
}
bool ChainPromiseNode::onReady(EventLoop::Event& event) noexcept {
switch (state) {
case PRE_STEP1:
case STEP1:
KJ_IREQUIRE(onReadyEvent == nullptr, "onReady() can only be called once.");
onReadyEvent = &event;
return false;
case STEP2:
return inner->onReady(event);
}
KJ_UNREACHABLE;
}
void ChainPromiseNode::get(ExceptionOrValue& output) noexcept {
KJ_IREQUIRE(state == STEP2);
return inner->get(output);
}
Maybe<const EventLoop&> ChainPromiseNode::getSafeEventLoop() noexcept {
return getEventLoop();
}
void ChainPromiseNode::fire() {
if (state == PRE_STEP1 && !inner->onReady(*this)) {
state = STEP1;
return;
}
KJ_IREQUIRE(state != STEP2);
static_assert(sizeof(Promise<int>) == sizeof(PromiseBase),
"This code assumes Promise<T> does not add any new members to PromiseBase.");
ExceptionOr<PromiseBase> intermediate;
inner->get(intermediate);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([this]() {
inner = nullptr;
})) {
intermediate.addException(kj::mv(*exception));
}
KJ_IF_MAYBE(exception, intermediate.exception) {
// There is an exception. If there is also a value, delete it.
kj::runCatchingExceptions([&,this]() { intermediate.value = nullptr; });
// Now set step2 to a rejected promise.
inner = heap<ImmediateBrokenPromiseNode>(kj::mv(*exception));
} else KJ_IF_MAYBE(value, intermediate.value) {
// There is a value and no exception. The value is itself a promise. Adopt it as our
// step2.
inner = kj::mv(value->node);
} else {
// We can only get here if inner->get() returned neither an exception nor a
// value, which never actually happens.
KJ_IASSERT(false, "Inner node returned empty value.");
}
state = STEP2;
if (onReadyEvent != nullptr) {
if (inner->onReady(*onReadyEvent)) {
onReadyEvent->arm();
}
}
}
CrossThreadPromiseNodeBase::CrossThreadPromiseNodeBase(
const EventLoop& loop, Own<PromiseNode>&& dependent, ExceptionOrValue& resultRef)
: Event(loop), dependent(kj::mv(dependent)), resultRef(resultRef) {
KJ_IREQUIRE(this->dependent->isSafeEventLoop(loop));
// The constructor may be called from any thread, so before we can even call onReady() we need
// to switch threads.
arm();
}
CrossThreadPromiseNodeBase::~CrossThreadPromiseNodeBase() noexcept(false) {
disarm();
}
bool CrossThreadPromiseNodeBase::onReady(EventLoop::Event& event) noexcept {
return PromiseNode::atomicOnReady(onReadyEvent, event);
}
Maybe<const EventLoop&> CrossThreadPromiseNodeBase::getSafeEventLoop() noexcept {
return nullptr;
}
void CrossThreadPromiseNodeBase::fire() {
if (!isWaiting && !this->dependent->onReady(*this)) {
isWaiting = true;
} else {
dependent->get(resultRef);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([this]() {
auto deleteMe = kj::mv(dependent);
})) {
resultRef.addException(kj::mv(*exception));
}
// If onReadyEvent is null, set it to _kJ_ALREADY_READY. Otherwise, arm it.
PromiseNode::atomicReady(onReadyEvent);
}
}
bool AdapterPromiseNodeBase::onReady(EventLoop::Event& event) noexcept {
return PromiseNode::atomicOnReady(onReadyEvent, event);
}
Maybe<const EventLoop&> AdapterPromiseNodeBase::getSafeEventLoop() noexcept {
// We're careful to be thread-safe so any thread is OK.
return nullptr;
}
} // namespace _ (private)
} // namespace kj
This diff is collapsed.
......@@ -149,12 +149,12 @@ namespace kj {
#define _kJ_NONNULL(nature, value, ...) \
(*({ \
auto result = ::kj::_::readMaybe(value); \
if (KJ_UNLIKELY(!result)) { \
auto _kj_result = ::kj::_::readMaybe(value); \
if (KJ_UNLIKELY(!_kj_result)) { \
::kj::_::Debug::Fault(__FILE__, __LINE__, ::kj::Exception::Nature::nature, 0, \
#value " != nullptr", #__VA_ARGS__, ##__VA_ARGS__).fatal(); \
} \
result; \
_kj_result; \
}))
#define KJ_ASSERT_NONNULL(value, ...) _kJ_NONNULL(LOCAL_BUG, value, ##__VA_ARGS__)
#define KJ_REQUIRE_NONNULL(value, ...) _kJ_NONNULL(PRECONDITION, value, ##__VA_ARGS__)
......
......@@ -126,6 +126,11 @@ public:
return *this;
}
inline Own& operator=(decltype(nullptr)) {
dispose();
return *this;
}
inline T* operator->() { return ptr; }
inline const T* operator->() const { return ptr; }
inline T& operator*() { return *ptr; }
......
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