Commit 06945999 authored by Kenton Varda's avatar Kenton Varda

Implement ability to fork promises.

parent e8d256ab
......@@ -316,5 +316,29 @@ TEST(Async, Ordering) {
EXPECT_EQ(7, counter);
}
TEST(Async, Fork) {
SimpleEventLoop loop;
auto outer = loop.evalLater([&]() {
Promise<String> promise = loop.evalLater([&]() { return str("foo"); });
auto fork = promise.fork();
auto branch1 = fork->addBranch().then([](const String& s) {
EXPECT_EQ("foo", s);
return 456;
});
auto branch2 = fork->addBranch().then([](const String& s) {
EXPECT_EQ("foo", s);
return 789;
});
EXPECT_EQ(456, loop.wait(kj::mv(branch1)));
EXPECT_EQ(789, loop.wait(kj::mv(branch2)));
});
loop.wait(kj::mv(outer));
}
} // namespace
} // namespace kj
......@@ -296,6 +296,8 @@ void PromiseNode::atomicReady(EventLoop::Event*& onReadyEvent,
}
}
// -------------------------------------------------------------------
bool ImmediatePromiseNodeBase::onReady(EventLoop::Event& event) noexcept { return true; }
Maybe<const EventLoop&> ImmediatePromiseNodeBase::getSafeEventLoop() noexcept { return nullptr; }
......@@ -306,6 +308,8 @@ 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)) {}
......@@ -326,6 +330,94 @@ Maybe<const EventLoop&> TransformPromiseNodeBase::getSafeEventLoop() noexcept {
return loop;
}
// -------------------------------------------------------------------
ForkBranchBase::ForkBranchBase(Own<ForkHubBase>&& hubParam): hub(kj::mv(hubParam)) {
auto lock = hub->branchList.lockExclusive();
if (lock->lastPtr == nullptr) {
onReadyEvent = _kJ_ALREADY_READY;
} else {
// Insert into hub's linked list of branches.
prevPtr = lock->lastPtr;
*prevPtr = this;
next = nullptr;
lock->lastPtr = &next;
}
}
ForkBranchBase::~ForkBranchBase() {
if (prevPtr != nullptr) {
// Remove from hub's linked list of branches.
auto lock = hub->branchList.lockExclusive();
*prevPtr = next;
(next == nullptr ? lock->lastPtr : next->prevPtr) = prevPtr;
}
}
void ForkBranchBase::hubReady() noexcept {
// TODO(soon): This should only yield if queuing cross-thread.
atomicReady(onReadyEvent, EventLoop::Event::YIELD);
}
void ForkBranchBase::releaseHub(ExceptionOrValue& output) {
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([this]() {
auto deleteMe = kj::mv(hub);
})) {
output.addException(kj::mv(*exception));
}
}
bool ForkBranchBase::onReady(EventLoop::Event& event) noexcept {
return atomicOnReady(onReadyEvent, event);
}
Maybe<const EventLoop&> ForkBranchBase::getSafeEventLoop() noexcept {
// It's safe to read the hub's value from multiple threads, once it is ready, since we'll only
// be reading a const reference.
return nullptr;
}
// -------------------------------------------------------------------
ForkHubBase::ForkHubBase(const EventLoop& loop, Own<PromiseNode>&& inner,
ExceptionOrValue& resultRef)
: EventLoop::Event(loop), inner(kj::mv(inner)), resultRef(resultRef) {
KJ_DREQUIRE(this->inner->isSafeEventLoop(loop));
// TODO(soon): This should only yield if queuing cross-thread.
arm(YIELD);
}
ForkHubBase::~ForkHubBase() noexcept(false) {}
void ForkHubBase::fire() {
if (!isWaiting && !inner->onReady(*this)) {
isWaiting = true;
} else {
// Dependency is ready. Fetch its result and then delete the node.
inner->get(resultRef);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([this]() {
auto deleteMe = kj::mv(inner);
})) {
resultRef.addException(kj::mv(*exception));
}
auto lock = branchList.lockExclusive();
for (auto branch = lock->first; branch != nullptr; branch = branch->next) {
branch->hubReady();
*branch->prevPtr = nullptr;
branch->prevPtr = nullptr;
}
*lock->lastPtr = nullptr;
// Indicate that the list is no longer active.
lock->lastPtr = nullptr;
}
}
// -------------------------------------------------------------------
ChainPromiseNode::ChainPromiseNode(const EventLoop& loop, Own<PromiseNode> inner, Schedule schedule)
: Event(loop), state(PRE_STEP1), inner(kj::mv(inner)) {
KJ_DREQUIRE(this->inner->isSafeEventLoop(loop));
......@@ -401,10 +493,12 @@ void ChainPromiseNode::fire() {
}
}
// -------------------------------------------------------------------
CrossThreadPromiseNodeBase::CrossThreadPromiseNodeBase(
const EventLoop& loop, Own<PromiseNode>&& dependent, ExceptionOrValue& resultRef)
: Event(loop), dependent(kj::mv(dependent)), resultRef(resultRef) {
KJ_DREQUIRE(this->dependent->isSafeEventLoop(loop));
const EventLoop& loop, Own<PromiseNode>&& dependency, ExceptionOrValue& resultRef)
: Event(loop), dependency(kj::mv(dependency)), resultRef(resultRef) {
KJ_DREQUIRE(this->dependency->isSafeEventLoop(loop));
// The constructor may be called from any thread, so before we can even call onReady() we need
// to switch threads. We yield here so that the event is added to the end of the queue, which
......@@ -426,12 +520,12 @@ Maybe<const EventLoop&> CrossThreadPromiseNodeBase::getSafeEventLoop() noexcept
}
void CrossThreadPromiseNodeBase::fire() {
if (!isWaiting && !this->dependent->onReady(*this)) {
if (!isWaiting && !dependency->onReady(*this)) {
isWaiting = true;
} else {
dependent->get(resultRef);
dependency->get(resultRef);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([this]() {
auto deleteMe = kj::mv(dependent);
auto deleteMe = kj::mv(dependency);
})) {
resultRef.addException(kj::mv(*exception));
}
......@@ -441,6 +535,8 @@ void CrossThreadPromiseNodeBase::fire() {
}
}
// -------------------------------------------------------------------
bool AdapterPromiseNodeBase::onReady(EventLoop::Event& event) noexcept {
return PromiseNode::atomicOnReady(onReadyEvent, event);
}
......
This diff is collapsed.
......@@ -766,8 +766,8 @@ public:
inline Maybe(const Maybe<const U&>& other) noexcept: ptr(other.ptr) {}
inline Maybe(decltype(nullptr)) noexcept: ptr(nullptr) {}
inline Maybe& operator=(T& other) noexcept { ptr = &other; }
inline Maybe& operator=(T* other) noexcept { ptr = other; }
inline Maybe& operator=(T& other) noexcept { ptr = &other; return *this; }
inline Maybe& operator=(T* other) noexcept { ptr = other; return *this; }
template <typename U>
inline Maybe& operator=(Maybe<U&>& other) noexcept { ptr = other.ptr; return *this; }
template <typename U>
......
......@@ -52,9 +52,6 @@ class Refcounted: private Disposer {
public:
virtual ~Refcounted() noexcept(false);
template <typename T>
static Own<T> addRef(T& object);
private:
mutable volatile uint refcount = 0;
......
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