Commit 5f6baed9 authored by Kenton Varda's avatar Kenton Varda

First tests of RPC protocol implementation.

parent 9575df0c
......@@ -112,6 +112,10 @@ kj::Own<const ClientHook> newBrokenCap(const char* reason) {
return kj::refcounted<BrokenClient>(reason);
}
kj::Own<const ClientHook> newBrokenCap(kj::Exception&& reason) {
return kj::refcounted<BrokenClient>(kj::mv(reason));
}
Arena::~Arena() noexcept(false) {}
BuilderArena::~BuilderArena() noexcept(false) {}
......@@ -412,20 +416,26 @@ SegmentBuilder* ImbuedBuilderArena::imbue(SegmentBuilder* baseSegment) {
result = &segment0;
} else {
auto lock = moreSegments.lockExclusive();
KJ_IF_MAYBE(segmentState, *lock) {
auto id = baseSegment->getSegmentId().value;
if (id >= segmentState->get()->builders.size()) {
segmentState->get()->builders.resize(id + 1);
}
KJ_IF_MAYBE(segment, segmentState->get()->builders[id]) {
result = *segment;
} else {
auto newBuilder = kj::heap<ImbuedSegmentBuilder>(this, baseSegment);
result = newBuilder;
segmentState->get()->builders[id] = kj::mv(newBuilder);
}
MultiSegmentState* segmentState;
KJ_IF_MAYBE(s, *lock) {
segmentState = *s;
} else {
auto newState = kj::heap<MultiSegmentState>();
segmentState = newState;
*lock = kj::mv(newState);
}
auto id = baseSegment->getSegmentId().value;
if (id >= segmentState->builders.size()) {
segmentState->builders.resize(id + 1);
}
KJ_IF_MAYBE(segment, segmentState->builders[id]) {
result = *segment;
} else {
auto newBuilder = kj::heap<ImbuedSegmentBuilder>(this, baseSegment);
result = newBuilder;
segmentState->builders[id] = kj::mv(newBuilder);
}
return nullptr;
}
KJ_DASSERT(result->getArray().begin() == baseSegment->getArray().begin());
......
......@@ -32,6 +32,7 @@
#include <unordered_map>
#include <kj/common.h>
#include <kj/mutex.h>
#include <kj/exception.h>
#include "common.h"
#include "message.h"
#include "layout.h"
......@@ -58,6 +59,7 @@ class Segment;
typedef kj::Id<uint32_t, Segment> SegmentId;
kj::Own<const ClientHook> newBrokenCap(const char* reason);
kj::Own<const ClientHook> newBrokenCap(kj::Exception&& reason);
// Helper function that creates a capability which simply throws exceptions when called.
// Implemented in arena.c++ rather than capability.c++ because it is needed by layout.c++ and we
// don't want capability.c++ to be required by people not using caps.
......
......@@ -77,6 +77,10 @@ kj::Own<const ClientHook> newBrokenCap(const char* reason) {
return _::newBrokenCap(reason);
}
kj::Own<const ClientHook> newBrokenCap(kj::Exception&& reason) {
return _::newBrokenCap(kj::mv(reason));
}
// =======================================================================================
namespace {
......@@ -384,6 +388,10 @@ public:
CallContext<ObjectPointer, ObjectPointer>(*contextPtr));
});
// Make sure that this client cannot be destroyed until the promise completes.
promise = eventLoop.there(kj::mv(promise), kj::mvCapture(kj::addRef(*this),
[=](kj::Own<const LocalClient>&& ref) {}));
// We have to fork this promise for the pipeline to receive a copy of the answer.
auto forked = eventLoop.fork(kj::mv(promise));
......@@ -393,7 +401,7 @@ public:
return kj::refcounted<LocalPipeline>(kj::mv(context));
}));
auto completionPromise = eventLoop.there(forked.addBranch(), kj::mvCapture(context->addRef(),
auto completionPromise = eventLoop.there(forked.addBranch(), kj::mvCapture(context,
[=](kj::Own<CallContextHook>&& context) {
// Nothing to do here. We just wanted to make sure to hold on to a reference to the
// context even if the pipeline was discarded.
......
......@@ -208,6 +208,12 @@ struct ObjectPointer {
inline Reader asReader() const { return Reader(builder.asReader()); }
inline operator Reader() const { return Reader(builder.asReader()); }
inline void setInternal(_::StructReader value) { builder.setStruct(value); }
// For internal use.
//
// TODO(cleanup): RPC implementation uses this, but wouldn't have to if we had an AnyStruct
// type, which would be useful anyawy.
private:
_::PointerBuilder builder;
friend class Orphanage;
......@@ -254,9 +260,15 @@ public:
Orphan(Orphan&&) = default;
Orphan& operator=(Orphan&&) = default;
template <typename T>
inline Orphan(Orphan<T>&& other): builder(kj::mv(other.builder)) {}
template <typename T>
inline Orphan& operator=(Orphan<T>&& other) { builder = kj::mv(other.builder); }
// Cast from typed orphan.
// It's not possible to get an ObjectPointer::{Reader,Builder} directly since there is no
// underlying pointer (the pointer would normally live in the parent, but this object is
// orphaned). It is possible, however, to request readers/builders.
// orphaned). It is possible, however, to request typed readers/builders.
template <typename T>
inline BuilderFor<T> getAs();
......
// 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 "rpc.h"
#include "test-util.h"
#include <kj/debug.h>
#include <gtest/gtest.h>
#include <map>
#include <queue>
namespace capnp {
namespace _ { // private
namespace {
class TestNetworkAdapter;
class TestNetwork {
public:
~TestNetwork() noexcept(false);
TestNetworkAdapter& add(kj::StringPtr name);
kj::Maybe<const TestNetworkAdapter&> find(kj::StringPtr name) const {
auto lock = map.lockShared();
auto iter = lock->find(name);
if (iter == lock->end()) {
return nullptr;
} else {
return *iter->second;
}
}
private:
kj::MutexGuarded<std::map<kj::StringPtr, kj::Own<TestNetworkAdapter>>> map;
};
typedef VatNetwork<
test::TestSturdyRef, test::TestProvisionId, test::TestRecipientId, test::TestThirdPartyCapId,
test::TestJoinAnswer> TestNetworkAdapterBase;
class TestNetworkAdapter final: public TestNetworkAdapterBase {
public:
TestNetworkAdapter(const TestNetwork& network): network(network) {}
typedef TestNetworkAdapterBase::Connection Connection;
class ConnectionImpl final: public Connection, public kj::Refcounted {
public:
ConnectionImpl() {}
void attach(ConnectionImpl& other) {
KJ_REQUIRE(partner == nullptr);
KJ_REQUIRE(other.partner == nullptr);
partner = other;
other.partner = *this;
}
class IncomingRpcMessageImpl final: public IncomingRpcMessage {
public:
IncomingRpcMessageImpl(uint firstSegmentWordSize)
: message(firstSegmentWordSize == 0 ? SUGGESTED_FIRST_SEGMENT_WORDS
: firstSegmentWordSize) {}
ObjectPointer::Reader getBody() override {
return message.getRoot<ObjectPointer>().asReader();
}
MallocMessageBuilder message;
};
class OutgoingRpcMessageImpl final: public OutgoingRpcMessage {
public:
OutgoingRpcMessageImpl(const ConnectionImpl& connection, uint firstSegmentWordSize)
: connection(connection),
message(kj::heap<IncomingRpcMessageImpl>(firstSegmentWordSize)) {}
ObjectPointer::Builder getBody() override {
return message->message.getRoot<ObjectPointer>();
}
void send() override {
KJ_IF_MAYBE(p, connection.partner) {
auto lock = p->queues.lockExclusive();
if (lock->fulfillers.empty()) {
lock->messages.push(kj::mv(message));
} else {
lock->fulfillers.front()->fulfill(kj::mv(message));
lock->fulfillers.pop();
}
}
}
private:
const ConnectionImpl& connection;
kj::Own<IncomingRpcMessageImpl> message;
};
kj::Own<OutgoingRpcMessage> newOutgoingMessage(uint firstSegmentWordSize) const override {
return kj::heap<OutgoingRpcMessageImpl>(*this, firstSegmentWordSize);
}
kj::Promise<kj::Own<IncomingRpcMessage>> receiveIncomingMessage() override {
auto lock = queues.lockExclusive();
if (lock->messages.empty()) {
auto paf = kj::newPromiseAndFulfiller<kj::Own<IncomingRpcMessage>>();
lock->fulfillers.push(kj::mv(paf.fulfiller));
return kj::mv(paf.promise);
} else {
auto result = kj::mv(lock->messages.front());
lock->messages.pop();
return kj::mv(result);
}
}
void introduceTo(Connection& recipient,
test::TestThirdPartyCapId::Builder sendToRecipient,
test::TestRecipientId::Builder sendToTarget) override {
KJ_FAIL_ASSERT("not implemented");
}
ConnectionAndProvisionId connectToIntroduced(
test::TestThirdPartyCapId::Reader capId) override {
KJ_FAIL_ASSERT("not implemented");
}
kj::Own<Connection> acceptIntroducedConnection(
test::TestRecipientId::Reader recipientId) override {
KJ_FAIL_ASSERT("not implemented");
}
private:
kj::Maybe<ConnectionImpl&> partner;
struct Queues {
std::queue<kj::Own<kj::PromiseFulfiller<kj::Own<IncomingRpcMessage>>>> fulfillers;
std::queue<kj::Own<IncomingRpcMessage>> messages;
};
kj::MutexGuarded<Queues> queues;
};
kj::Own<Connection> connectToHostOf(test::TestSturdyRef::Reader ref) override {
const TestNetworkAdapter& dst = KJ_REQUIRE_NONNULL(network.find(ref.getHost()));
kj::Locked<State> myLock;
kj::Locked<State> dstLock;
if (&dst < this) {
dstLock = dst.state.lockExclusive();
myLock = state.lockExclusive();
} else {
myLock = state.lockExclusive();
dstLock = dst.state.lockExclusive();
}
auto iter = myLock->connections.find(&dst);
if (iter == myLock->connections.end()) {
auto local = kj::refcounted<ConnectionImpl>();
auto remote = kj::refcounted<ConnectionImpl>();
local->attach(*remote);
myLock->connections[&dst] = kj::addRef(*local);
dstLock->connections[this] = kj::addRef(*remote);
if (dstLock->fulfillerQueue.empty()) {
dstLock->connectionQueue.push(kj::mv(remote));
} else {
dstLock->fulfillerQueue.front()->fulfill(kj::mv(remote));
dstLock->fulfillerQueue.pop();
}
return kj::mv(local);
} else {
return kj::addRef(*iter->second);
}
}
kj::Promise<kj::Own<Connection>> acceptConnectionAsRefHost() override {
auto lock = state.lockExclusive();
if (lock->connectionQueue.empty()) {
auto paf = kj::newPromiseAndFulfiller<kj::Own<Connection>>();
lock->fulfillerQueue.push(kj::mv(paf.fulfiller));
return kj::mv(paf.promise);
} else {
auto result = kj::mv(lock->connectionQueue.front());
lock->connectionQueue.pop();
return kj::mv(result);
}
}
private:
const TestNetwork& network;
struct State {
std::map<const TestNetworkAdapter*, kj::Own<ConnectionImpl>> connections;
std::queue<kj::Own<kj::PromiseFulfiller<kj::Own<Connection>>>> fulfillerQueue;
std::queue<kj::Own<Connection>> connectionQueue;
};
kj::MutexGuarded<State> state;
};
TestNetwork::~TestNetwork() noexcept(false) {}
TestNetworkAdapter& TestNetwork::add(kj::StringPtr name) {
auto lock = map.lockExclusive();
return *((*lock)[name] = kj::heap<TestNetworkAdapter>(*this));
}
// =======================================================================================
class TestInterfaceImpl final: public test::TestInterface::Server {
public:
TestInterfaceImpl(int& callCount): callCount(callCount) {}
int& callCount;
::kj::Promise<void> foo(
test::TestInterface::FooParams::Reader params,
test::TestInterface::FooResults::Builder result) override {
++callCount;
EXPECT_EQ(123, params.getI());
EXPECT_TRUE(params.getJ());
result.setX("foo");
return kj::READY_NOW;
}
::kj::Promise<void> bazAdvanced(
::capnp::CallContext<test::TestInterface::BazParams,
test::TestInterface::BazResults> context) override {
++callCount;
auto params = context.getParams();
checkTestMessage(params.getS());
context.releaseParams();
EXPECT_ANY_THROW(context.getParams());
return kj::READY_NOW;
}
};
class TestExtendsImpl final: public test::TestExtends::Server {
public:
TestExtendsImpl(int& callCount): callCount(callCount) {}
int& callCount;
::kj::Promise<void> foo(
test::TestInterface::FooParams::Reader params,
test::TestInterface::FooResults::Builder result) override {
++callCount;
EXPECT_EQ(321, params.getI());
EXPECT_FALSE(params.getJ());
result.setX("bar");
return kj::READY_NOW;
}
::kj::Promise<void> graultAdvanced(
::capnp::CallContext<test::TestExtends::GraultParams, test::TestAllTypes> context) override {
++callCount;
context.releaseParams();
initTestMessage(context.getResults());
return kj::READY_NOW;
}
};
class TestPipelineImpl final: public test::TestPipeline::Server {
public:
TestPipelineImpl(int& callCount): callCount(callCount) {}
int& callCount;
::kj::Promise<void> getCapAdvanced(
capnp::CallContext<test::TestPipeline::GetCapParams,
test::TestPipeline::GetCapResults> context) override {
++callCount;
auto params = context.getParams();
EXPECT_EQ(234, params.getN());
auto cap = params.getInCap();
context.releaseParams();
auto request = cap.fooRequest();
request.setI(123);
request.setJ(true);
return request.send().then(
[this,context](capnp::Response<test::TestInterface::FooResults>&& response) mutable {
EXPECT_EQ("foo", response.getX());
auto result = context.getResults();
result.setS("bar");
result.initOutBox().setCap(kj::heap<TestExtendsImpl>(callCount));
});
}
};
class TestRestorer final: public SturdyRefRestorer<test::TestSturdyRef> {
public:
int callCount = 0;
Capability::Client restore(test::TestSturdyRef::Reader ref) override {
switch (ref.getTag()) {
case test::TestSturdyRef::Tag::TEST_INTERFACE:
return kj::heap<TestInterfaceImpl>(callCount);
case test::TestSturdyRef::Tag::TEST_PIPELINE:
return kj::heap<TestPipelineImpl>(callCount);
}
KJ_UNREACHABLE;
}
};
class RpcTest: public testing::Test {
protected:
TestNetwork network;
TestRestorer restorer;
kj::SimpleEventLoop loop;
RpcSystem<test::TestSturdyRef> rpcClient;
RpcSystem<test::TestSturdyRef> rpcServer;
Capability::Client connect(test::TestSturdyRef::Tag tag) {
MallocMessageBuilder refMessage(128);
auto ref = refMessage.initRoot<test::TestSturdyRef>();
ref.setHost("server");
ref.setTag(tag);
return rpcClient.connect(ref);
}
RpcTest()
: rpcClient(makeRpcClient(network.add("client"), loop)),
rpcServer(makeRpcServer(network.add("server"), restorer, loop)) {}
~RpcTest() noexcept {}
// Need to declare this with explicit noexcept otherwise it conflicts with testing::Test::~Test.
// (Urgh, C++11, why did you change this?)
};
TEST_F(RpcTest, Basic) {
auto client = connect(test::TestSturdyRef::Tag::TEST_INTERFACE).castAs<test::TestInterface>();
auto request1 = client.fooRequest();
request1.setI(123);
request1.setJ(true);
auto promise1 = request1.send();
auto request2 = client.bazRequest();
initTestMessage(request2.initS());
auto promise2 = request2.send();
bool barFailed = false;
auto request3 = client.barRequest();
auto promise3 = loop.there(request3.send(),
[](Response<test::TestInterface::BarResults>&& response) {
ADD_FAILURE() << "Expected bar() call to fail.";
}, [&](kj::Exception&& e) {
barFailed = true;
});
EXPECT_EQ(0, restorer.callCount);
auto response1 = loop.wait(kj::mv(promise1));
EXPECT_EQ("foo", response1.getX());
auto response2 = loop.wait(kj::mv(promise2));
loop.wait(kj::mv(promise3));
EXPECT_EQ(2, restorer.callCount);
EXPECT_TRUE(barFailed);
}
TEST_F(RpcTest, Pipelining) {
auto client = connect(test::TestSturdyRef::Tag::TEST_PIPELINE).castAs<test::TestPipeline>();
int chainedCallCount = 0;
auto request = client.getCapRequest();
request.setN(234);
request.setInCap(test::TestInterface::Client(
kj::heap<TestInterfaceImpl>(chainedCallCount), loop));
auto promise = request.send();
auto pipelineRequest = promise.getOutBox().getCap().fooRequest();
pipelineRequest.setI(321);
auto pipelinePromise = pipelineRequest.send();
auto pipelineRequest2 = promise.getOutBox().getCap().castAs<test::TestExtends>().graultRequest();
auto pipelinePromise2 = pipelineRequest2.send();
promise = nullptr; // Just to be annoying, drop the original promise.
EXPECT_EQ(0, restorer.callCount);
EXPECT_EQ(0, chainedCallCount);
auto response = loop.wait(kj::mv(pipelinePromise));
EXPECT_EQ("bar", response.getX());
auto response2 = loop.wait(kj::mv(pipelinePromise2));
checkTestMessage(response2);
EXPECT_EQ(3, restorer.callCount);
EXPECT_EQ(1, chainedCallCount);
}
} // namespace
} // namespace _ (private)
} // namespace capnp
......@@ -65,6 +65,7 @@ kj::Maybe<kj::Array<PipelineOp>> toPipelineOps(List<rpc::PromisedAnswer::Op>::Re
return nullptr;
}
}
result.add(op);
}
return result.finish();
}
......@@ -220,13 +221,6 @@ struct Question {
// received and `retainedCaps` processed. (If this is non-null, then the call has not returned
// yet.)
kj::Maybe<RpcPipeline&> pipeline;
// The local pipeline object. The RpcPipeline's own destructor sets this value to null.
//
// TODO(cleanup): We only have this pointer here because CapInjectorImpl::getInjectedCap() needs
// it, but perhaps CapInjectorImpl should instead hold on to the ClientHook it got in the first
// place.
bool isStarted = false;
// Is this Question ID currently in-use? (This is true until both `Return` has been received and
// `Finish` has been sent.)
......@@ -275,16 +269,56 @@ struct Import {
// =======================================================================================
class RpcConnectionState final: public kj::TaskSet::ErrorHandler {
class PromisedAnswerClient;
public:
RpcConnectionState(const kj::EventLoop& eventLoop,
kj::Maybe<SturdyRefRestorerBase&> restorer,
kj::Own<VatNetworkBase::Connection>&& connection)
: eventLoop(eventLoop), connection(kj::mv(connection)), tasks(eventLoop, *this),
exportDisposer(*this) {
: eventLoop(eventLoop), restorer(restorer), connection(kj::mv(connection)),
tasks(eventLoop, *this), exportDisposer(*this) {
tasks.add(messageLoop());
}
kj::Own<const ClientHook> restore(_::StructReader ref) {
// TODO(now)
QuestionId questionId;
auto paf = kj::newPromiseAndFulfiller<kj::Own<RpcResponse>>(eventLoop);
{
auto lock = tables.lockExclusive();
auto& question = lock->questions.next(questionId);
question.isStarted = true;
question.fulfiller = kj::mv(paf.fulfiller);
// We need a dummy paramCaps since null normally indicates that the question has completed.
question.paramCaps = kj::heap<CapInjectorImpl>(*this);
}
{
auto message = connection->newOutgoingMessage(
ref.totalSize() / WORDS + messageSizeHint<rpc::Restore>());
auto builder = message->getBody().initAs<rpc::Message>().initRestore();
builder.setQuestionId(questionId);
builder.getRef().setInternal(ref);
message->send();
}
auto questionRef = kj::refcounted<QuestionRef>(*this, questionId);
auto promiseWithQuestionRef = eventLoop.there(kj::mv(paf.promise),
kj::mvCapture(kj::addRef(*questionRef),
[](kj::Own<const QuestionRef>&& questionRef, kj::Own<RpcResponse>&& response)
-> kj::Own<const RpcResponse> {
response->setQuestionRef(kj::mv(questionRef));
return kj::mv(response);
}));
auto pipeline = kj::refcounted<RpcPipeline>(
*this, questionId, eventLoop.fork(kj::mv(promiseWithQuestionRef)));
return kj::refcounted<PromisedAnswerClient>(*this, kj::mv(pipeline), nullptr);
}
void taskFailed(kj::Exception&& exception) override {
......@@ -295,10 +329,13 @@ public:
// - All imported promises resolve to exceptions.
// - Send abort message.
// - Remove from connection map.
kj::throwRecoverableException(kj::mv(exception));
}
private:
const kj::EventLoop& eventLoop;
kj::Maybe<SturdyRefRestorerBase&> restorer;
kj::Own<VatNetworkBase::Connection> connection;
class ImportClient;
......@@ -403,7 +440,7 @@ private:
auto pipeline = promise.releasePipelineHook();
auto voidPromise = promise.then(kj::mvCapture(context,
auto voidPromise = promise.thenInAnyThread(kj::mvCapture(context,
[](kj::Own<CallContextHook>&& context, Response<ObjectPointer> response) {
size_t sizeHint = response.targetSizeInWords();
......@@ -488,6 +525,7 @@ private:
}
// implements ClientHook -----------------------------------------
Request<ObjectPointer, ObjectPointer> newCall(
uint64_t interfaceId, uint16_t methodId, uint firstSegmentWordSize) const override {
auto request = kj::heap<RpcRequest>(connectionState, firstSegmentWordSize, kj::addRef(*this));
......@@ -529,7 +567,7 @@ private:
fork(nullptr) {
auto paf = kj::newPromiseAndFulfiller<kj::Own<const ClientHook>>(connectionState.eventLoop);
fulfiller = kj::mv(paf.fulfiller);
fork = paf.promise.fork();
fork = connectionState.eventLoop.fork(kj::mv(paf.promise));
}
bool settle(kj::Own<const ClientHook> replacement) override {
......@@ -560,7 +598,7 @@ private:
kj::Own<const RpcPipeline>&& pipeline,
kj::Array<PipelineOp>&& ops)
: RpcClient(connectionState), ops(kj::mv(ops)),
resolveSelfPromise(pipeline->onResponse().then(
resolveSelfPromise(connectionState.eventLoop.there(pipeline->onResponse(),
[this](kj::Own<const RpcResponse>&& response) -> kj::Promise<void> {
resolve(kj::mv(response));
return kj::READY_NOW; // hack to force eager resolution.
......@@ -626,8 +664,9 @@ private:
auto lock = state.lockShared();
if (lock->is<Waiting>()) {
return lock->get<Waiting>()->onResponse().then(kj::mvCapture(kj::heapArray(ops.asPtr()),
[](kj::Array<PipelineOp>&& ops, kj::Own<const RpcResponse>&& response) {
return lock->get<Waiting>()->onResponse().thenInAnyThread(
kj::mvCapture(kj::heapArray(ops.asPtr()),
[](kj::Array<PipelineOp>&& ops, kj::Own<const RpcResponse>&& response) {
return response->getResults().getPipelinedCap(ops);
}));
} else if (lock->is<Resolved>()) {
......@@ -719,7 +758,7 @@ private:
: connectionState(connectionState) {}
~CapExtractorImpl() noexcept(false) {
KJ_ASSERT(retainedCaps.getWithoutLock().size() > 0,
KJ_ASSERT(retainedCaps.getWithoutLock().size() == 0,
"CapExtractorImpl destroyed without getting a chance to retain the caps!") {
break;
}
......@@ -795,9 +834,9 @@ private:
if (descriptor.which() == rpc::CapDescriptor::SENDER_PROMISE) {
// TODO(now): Check for pending `Resolve` messages replacing this import ID, and if
// one exists, use that client instead.
kj::refcounted<PromiseImportClient>(connectionState, importId);
result = kj::refcounted<PromiseImportClient>(connectionState, importId);
} else {
kj::refcounted<SettledImportClient>(connectionState, importId);
result = kj::refcounted<SettledImportClient>(connectionState, importId);
}
import.client = result;
......@@ -885,7 +924,7 @@ private:
for (auto& entry: caps.getWithoutLock()) {
KJ_IF_MAYBE(exportId, connectionState.writeDescriptor(
kj::mv(entry.second.cap), entry.second.builder, tables)) {
entry.second.cap->addRef(), entry.second.builder, tables)) {
exports.add(*exportId);
}
}
......@@ -899,7 +938,6 @@ private:
auto result = lock->insert(std::make_pair(
identity(descriptor), CapInfo(descriptor, kj::mv(cap))));
KJ_REQUIRE(result.second, "A cap has already been injected at this location.") {
result.first->second.cap = kj::mv(cap);
break;
}
}
......@@ -1057,19 +1095,20 @@ private:
auto questionRef = kj::refcounted<QuestionRef>(connectionState, questionId);
auto promiseWithQuestionRef = promise.then(kj::mvCapture(kj::addRef(*questionRef),
auto promiseWithQuestionRef = promise.thenInAnyThread(kj::mvCapture(kj::addRef(*questionRef),
[](kj::Own<const QuestionRef>&& questionRef, kj::Own<RpcResponse>&& response)
-> kj::Own<const RpcResponse> {
response->setQuestionRef(kj::mv(questionRef));
return kj::mv(response);
}));
auto forkedPromise = promiseWithQuestionRef.fork();
auto forkedPromise = connectionState.eventLoop.fork(kj::mv(promiseWithQuestionRef));
auto appPromise = forkedPromise.addBranch().then([](kj::Own<const RpcResponse>&& response) {
auto reader = response->getResults();
return Response<ObjectPointer>(reader, kj::mv(response));
});
auto appPromise = forkedPromise.addBranch().thenInAnyThread(
[](kj::Own<const RpcResponse>&& response) {
auto reader = response->getResults();
return Response<ObjectPointer>(reader, kj::mv(response));
});
auto pipeline = kj::refcounted<RpcPipeline>(
connectionState, questionId, kj::mv(forkedPromise));
......@@ -1096,7 +1135,7 @@ private:
kj::ForkedPromise<kj::Own<const RpcResponse>>&& redirectLaterParam)
: connectionState(connectionState),
redirectLater(kj::mv(redirectLaterParam)),
resolveSelfPromise(redirectLater.addBranch().then(
resolveSelfPromise(connectionState.eventLoop.there(redirectLater.addBranch(),
[this](kj::Own<const RpcResponse>&& response) -> kj::Promise<void> {
resolve(kj::mv(response));
return kj::READY_NOW; // hack to force eager resolution.
......@@ -1304,7 +1343,7 @@ private:
builder.setQuestionId(questionId);
builder.adoptRetainedCaps(requestCapExtractor.finalizeRetainedCaps(
Orphanage::getForMessageContaining(returnMessage)));
Orphanage::getForMessageContaining(builder)));
fromException(exception, builder.initException());
message->send();
......@@ -1318,7 +1357,7 @@ private:
builder.setQuestionId(questionId);
builder.adoptRetainedCaps(requestCapExtractor.finalizeRetainedCaps(
Orphanage::getForMessageContaining(returnMessage)));
Orphanage::getForMessageContaining(builder)));
builder.setCanceled();
message->send();
......@@ -1515,10 +1554,12 @@ private:
// Message handling
kj::Promise<void> messageLoop() {
return connection->receiveIncomingMessage().then(
auto receive = eventLoop.there(connection->receiveIncomingMessage(),
[this](kj::Own<IncomingRpcMessage>&& message) {
handleMessage(kj::mv(message));
}).then([this]() {
});
return eventLoop.there(kj::mv(receive),
[this]() {
// No exceptions; continue loop.
//
// (We do this in a separate continuation to handle the case where exceptions are
......@@ -1558,6 +1599,10 @@ private:
// TODO(now)
break;
case rpc::Message::RESTORE:
handleRestore(kj::mv(message), reader.getRestore());
break;
default: {
auto message = connection->newOutgoingMessage(
reader.totalSizeInWords() + messageSizeHint<void>());
......@@ -1584,6 +1629,9 @@ private:
kj::throwRecoverableException(toException(exception));
}
// ---------------------------------------------------------------------------
// Level 0
void handleCall(kj::Own<IncomingRpcMessage>&& message, const rpc::Call::Reader& call) {
kj::Own<const ClientHook> capability;
......@@ -1670,10 +1718,10 @@ private:
// this tricks the promise framework into making sure the continuations actually run
// without anyone waiting on them. We should find a cleaner way to do this.
answer.asyncOp = promiseAndPipeline.promise.then(
kj::mvCapture(context, [this](kj::Own<RpcCallContext>&& context) -> kj::Promise<void> {
kj::mvCapture(context, [](kj::Own<RpcCallContext>&& context) -> kj::Promise<void> {
context->sendReturn();
return kj::READY_NOW;
}), [this,contextPtr](kj::Exception&& exception) -> kj::Promise<void> {
}), [contextPtr](kj::Exception&& exception) -> kj::Promise<void> {
contextPtr->sendErrorReturn(kj::mv(exception));
return kj::READY_NOW;
});
......@@ -1758,6 +1806,81 @@ private:
}
}
// ---------------------------------------------------------------------------
// Level 1
// ---------------------------------------------------------------------------
// Level 2
class SingleCapPipeline: public PipelineHook, public kj::Refcounted {
public:
SingleCapPipeline(kj::Own<const ClientHook>&& cap): cap(kj::mv(cap)) {}
kj::Own<const PipelineHook> addRef() const override {
return kj::addRef(*this);
}
kj::Own<const ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) const override {
if (ops.size() == 0) {
return cap->addRef();
} else {
return newBrokenCap("Invalid pipeline transform.");
}
}
private:
kj::Own<const ClientHook> cap;
};
void handleRestore(kj::Own<IncomingRpcMessage>&& message, const rpc::Restore::Reader& restore) {
QuestionId questionId = restore.getQuestionId();
auto response = connection->newOutgoingMessage(
messageSizeHint<rpc::Return>() + sizeInWords<rpc::CapDescriptor>() + 32);
rpc::Return::Builder ret = response->getBody().getAs<rpc::Message>().initReturn();
ret.setQuestionId(questionId);
CapInjectorImpl injector(*this);
CapBuilderContext context(injector);
kj::Own<const ClientHook> capHook;
// Call the restorer and initialize the answer.
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
KJ_IF_MAYBE(r, restorer) {
Capability::Client cap = r->baseRestore(restore.getRef());
auto answer = context.imbue(ret.initAnswer());
answer.setAs<Capability>(cap);
capHook = answer.asReader().getPipelinedCap(nullptr);
} else {
KJ_FAIL_REQUIRE("This vat cannot restore this SturdyRef.") { break; }
}
})) {
fromException(*exception, ret.initException());
capHook = newBrokenCap(kj::mv(*exception));
}
message = nullptr;
// Add the answer to the answer table for pipelining and send the response.
{
auto lock = tables.lockExclusive();
auto& answer = lock->answers[questionId];
KJ_REQUIRE(!answer.active, "questionId is already in use") {
return;
}
answer.active = true;
answer.pipeline = kj::Own<const PipelineHook>(
kj::refcounted<SingleCapPipeline>(kj::mv(capHook)));
injector.finishDescriptors(*lock);
response->send();
}
}
// =====================================================================================
void sendReleaseLater(ExportId importId, uint remoteRefcount) const {
......@@ -1773,40 +1896,84 @@ private:
} // namespace
class RpcSystemBase::Impl {
class RpcSystemBase::Impl final: public kj::TaskSet::ErrorHandler {
public:
Impl(VatNetworkBase& network, SturdyRefRestorerBase& restorer, const kj::EventLoop& eventLoop)
: network(network), restorer(restorer), eventLoop(eventLoop) {}
Impl(VatNetworkBase& network, kj::Maybe<SturdyRefRestorerBase&> restorer,
const kj::EventLoop& eventLoop)
: network(network), restorer(restorer), eventLoop(eventLoop), tasks(eventLoop, *this) {
tasks.add(acceptLoop());
}
~Impl() noexcept(false) {
// std::unordered_map doesn't like it when elements' destructors throw, so carefully
// disassemble it.
auto& connectionMap = connections.getWithoutLock();
if (!connectionMap.empty()) {
kj::Vector<kj::Own<RpcConnectionState>> deleteMe(connectionMap.size());
for (auto& entry: connectionMap) {
deleteMe.add(kj::mv(entry.second));
}
}
}
Capability::Client connect(_::StructReader ref) {
auto connection = network.baseConnectToHostOf(ref);
auto lock = connections.lockExclusive();
auto iter = lock->find(connection);
RpcConnectionState* state;
if (iter == lock->end()) {
VatNetworkBase::Connection* connectionPtr = connection;
auto newState = kj::heap<RpcConnectionState>(eventLoop, kj::mv(connection));
state = newState;
lock->insert(std::make_pair(connectionPtr, kj::mv(newState)));
} else {
state = iter->second;
}
auto& state = getConnectionState(kj::mv(connection), *lock);
return Capability::Client(state.restore(ref));
}
void taskFailed(kj::Exception&& exception) override {
// TODO(now): What do we do?
return Capability::Client(state->restore(ref));
kj::throwRecoverableException(kj::mv(exception));
}
private:
VatNetworkBase& network;
SturdyRefRestorerBase& restorer;
kj::Maybe<SturdyRefRestorerBase&> restorer;
const kj::EventLoop& eventLoop;
kj::TaskSet tasks;
typedef std::unordered_map<VatNetworkBase::Connection*, kj::Own<RpcConnectionState>>
ConnectionMap;
kj::MutexGuarded<ConnectionMap> connections;
kj::MutexGuarded<std::unordered_map<VatNetworkBase::Connection*, kj::Own<RpcConnectionState>>>
connections;
RpcConnectionState& getConnectionState(kj::Own<VatNetworkBase::Connection>&& connection,
ConnectionMap& lockedMap) {
auto iter = lockedMap.find(connection);
if (iter == lockedMap.end()) {
VatNetworkBase::Connection* connectionPtr = connection;
auto newState = kj::heap<RpcConnectionState>(eventLoop, restorer, kj::mv(connection));
RpcConnectionState& result = *newState;
lockedMap.insert(std::make_pair(connectionPtr, kj::mv(newState)));
return result;
} else {
return *iter->second;
}
}
kj::Promise<void> acceptLoop() {
auto receive = eventLoop.there(network.baseAcceptConnectionAsRefHost(),
[this](kj::Own<VatNetworkBase::Connection>&& connection) {
auto lock = connections.lockExclusive();
getConnectionState(kj::mv(connection), *lock);
});
return eventLoop.there(kj::mv(receive),
[this]() {
// No exceptions; continue loop.
//
// (We do this in a separate continuation to handle the case where exceptions are
// disabled.)
tasks.add(acceptLoop());
});
}
};
RpcSystemBase::RpcSystemBase(VatNetworkBase& network, SturdyRefRestorerBase& restorer,
RpcSystemBase::RpcSystemBase(VatNetworkBase& network, kj::Maybe<SturdyRefRestorerBase&> restorer,
const kj::EventLoop& eventLoop)
: impl(kj::heap<Impl>(network, restorer, eventLoop)) {}
RpcSystemBase::RpcSystemBase(RpcSystemBase&& other) = default;
RpcSystemBase::~RpcSystemBase() noexcept(false) {}
Capability::Client RpcSystemBase::baseConnect(_::StructReader ref) {
......
......@@ -35,6 +35,8 @@ namespace capnp {
// ***************************************************************************************
// =======================================================================================
// TODO(cleanup): Put these in rpc-internal.h?
class OutgoingRpcMessage;
class IncomingRpcMessage;
......@@ -78,8 +80,9 @@ public:
class RpcSystemBase {
public:
RpcSystemBase(VatNetworkBase& network, SturdyRefRestorerBase& restorer,
RpcSystemBase(VatNetworkBase& network, kj::Maybe<SturdyRefRestorerBase&> restorer,
const kj::EventLoop& eventLoop);
RpcSystemBase(RpcSystemBase&& other);
~RpcSystemBase() noexcept(false);
private:
......@@ -189,7 +192,7 @@ public:
// on the new connection will be an `Accept` message.
private:
void baseIntroduceTo(Connection& recipient,
void baseIntroduceTo(VatNetworkBase::Connection& recipient,
ObjectPointer::Builder sendToRecipient,
ObjectPointer::Builder sendToTarget) override final;
_::VatNetworkBase::ConnectionAndProvisionId baseConnectToIntroduced(
......@@ -232,7 +235,8 @@ class SturdyRefRestorer: public _::SturdyRefRestorerBase {
public:
virtual Capability::Client restore(typename SturdyRef::Reader ref) = 0;
// Restore the given SturdyRef, returning a capability representing it.
// Restore the given SturdyRef, returning a capability representing it. This is guaranteed only
// to be called on the RpcSystem's EventLoop's thread.
private:
Capability::Client baseRestore(ObjectPointer::Reader ref) override final;
......@@ -246,6 +250,7 @@ public:
RpcSystem(
VatNetwork<OutgoingSturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>& network,
kj::Maybe<SturdyRefRestorer<IncomingSturdyRef>&> restorer, const kj::EventLoop& eventLoop);
RpcSystem(RpcSystem&& other) = default;
Capability::Client connect(typename OutgoingSturdyRef::Reader ref);
// Restore the given SturdyRef from the network and return the capability representing it.
......@@ -255,7 +260,7 @@ template <typename OutgoingSturdyRef, typename IncomingSturdyRef,
typename ProvisionId, typename RecipientId, typename ThirdPartyCapId, typename JoinAnswer>
RpcSystem<OutgoingSturdyRef, IncomingSturdyRef> makeRpcServer(
VatNetwork<OutgoingSturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>& network,
kj::Maybe<SturdyRefRestorer<IncomingSturdyRef>&> restorer,
SturdyRefRestorer<IncomingSturdyRef>& restorer,
const kj::EventLoop& eventLoop = kj::EventLoop::current());
// Make an RPC server. Typical usage (e.g. in a main() function):
//
......@@ -270,7 +275,7 @@ template <typename OutgoingSturdyRef, typename ProvisionId,
RpcSystem<OutgoingSturdyRef> makeRpcClient(
VatNetwork<OutgoingSturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>& network,
const kj::EventLoop& eventLoop = kj::EventLoop::current());
// Make an RPC server. Typical usage (e.g. in a main() function):
// Make an RPC client. Typical usage (e.g. in a main() function):
//
// MyEventLoop eventLoop;
// MyNetwork network(eventLoop);
......@@ -289,10 +294,10 @@ RpcSystem<OutgoingSturdyRef> makeRpcClient(
template <typename SturdyRef, typename ProvisionId, typename RecipientId,
typename ThirdPartyCapId, typename JoinAnswer>
void VatNetwork<SturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>::
Connection::baseIntroduceTo(Connection& recipient,
Connection::baseIntroduceTo(VatNetworkBase::Connection& recipient,
ObjectPointer::Builder sendToRecipient,
ObjectPointer::Builder sendToTarget) {
introduceTo(recipient, sendToRecipient.initAs<ThirdPartyCapId>(),
introduceTo(kj::downcast<Connection>(recipient), sendToRecipient.initAs<ThirdPartyCapId>(),
sendToTarget.initAs<RecipientId>());
}
......@@ -351,6 +356,22 @@ Capability::Client RpcSystem<OutgoingSturdyRef, IncomingSturdyRef>::connect(
return baseConnect(_::PointerHelpers<OutgoingSturdyRef>::getInternalReader(ref));
}
template <typename OutgoingSturdyRef, typename IncomingSturdyRef,
typename ProvisionId, typename RecipientId, typename ThirdPartyCapId, typename JoinAnswer>
RpcSystem<OutgoingSturdyRef, IncomingSturdyRef> makeRpcServer(
VatNetwork<OutgoingSturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>& network,
SturdyRefRestorer<IncomingSturdyRef>& restorer, const kj::EventLoop& eventLoop) {
return RpcSystem<OutgoingSturdyRef, IncomingSturdyRef>(network, restorer, eventLoop);
}
template <typename OutgoingSturdyRef, typename ProvisionId,
typename RecipientId, typename ThirdPartyCapId, typename JoinAnswer>
RpcSystem<OutgoingSturdyRef> makeRpcClient(
VatNetwork<OutgoingSturdyRef, ProvisionId, RecipientId, ThirdPartyCapId, JoinAnswer>& network,
const kj::EventLoop& eventLoop) {
return RpcSystem<OutgoingSturdyRef>(network, nullptr, eventLoop);
}
} // namespace capnp
#endif // CAPNP_RPC_H_
......@@ -593,3 +593,17 @@ interface TestPipeline {
cap @0 :TestInterface;
}
}
struct TestSturdyRef {
host @0 :Text;
tag @1 :Tag;
enum Tag {
testInterface @0;
testPipeline @1;
}
}
struct TestProvisionId {}
struct TestRecipientId {}
struct TestThirdPartyCapId {}
struct TestJoinAnswer {}
......@@ -238,6 +238,15 @@ TEST(Async, SeparateFulfillerChained) {
#define EXPECT_ANY_THROW(code) EXPECT_DEATH(code, ".")
#endif
TEST(Async, SeparateFulfillerDiscarded) {
SimpleEventLoop loop;
auto pair = newPromiseAndFulfiller<int>();
pair.fulfiller = nullptr;
EXPECT_ANY_THROW(loop.wait(kj::mv(pair.promise)));
}
TEST(Async, Threads) {
EXPECT_ANY_THROW(EventLoop::current());
......
......@@ -23,6 +23,7 @@
#include "async.h"
#include "debug.h"
#include "vector.h"
#include <exception>
#include <map>
......@@ -271,6 +272,17 @@ public:
inline Impl(const EventLoop& loop, ErrorHandler& errorHandler)
: loop(loop), errorHandler(errorHandler) {}
~Impl() noexcept(false) {
// std::map doesn't like it when elements' destructors throw, so carefully disassemble it.
auto& taskMap = tasks.getWithoutLock();
if (!taskMap.empty()) {
Vector<Own<Task>> deleteMe(taskMap.size());
for (auto& entry: taskMap) {
deleteMe.add(kj::mv(entry.second));
}
}
}
class Task final: public EventLoop::Event {
public:
Task(const Impl& taskSet, Own<_::PromiseNode>&& nodeParam)
......@@ -281,6 +293,10 @@ public:
}
}
~Task() {
disarm();
}
protected:
void fire() override {
// Get the result.
......@@ -387,6 +403,7 @@ bool TransformPromiseNodeBase::onReady(EventLoop::Event& event) noexcept {
void TransformPromiseNodeBase::get(ExceptionOrValue& output) noexcept {
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
getImpl(output);
dropDependency();
})) {
output.addException(kj::mv(*exception));
}
......
......@@ -1242,29 +1242,33 @@ public:
: adapter(static_cast<PromiseFulfiller<UnfixVoid<T>>&>(*this), kj::fwd<Params>(params)...) {}
void get(ExceptionOrValue& output) noexcept override {
KJ_IREQUIRE(!isWaiting());
output.as<T>() = kj::mv(result);
}
private:
Adapter adapter;
ExceptionOr<T> result;
bool waiting = true;
void fulfill(T&& value) override {
if (isWaiting()) {
if (waiting) {
waiting = false;
result = ExceptionOr<T>(kj::mv(value));
setReady();
}
}
void reject(Exception&& exception) override {
if (isWaiting()) {
if (waiting) {
waiting = false;
result = ExceptionOr<T>(false, kj::mv(exception));
setReady();
}
}
bool isWaiting() override {
return result.value == nullptr && result.exception == nullptr;
return waiting;
}
};
......@@ -1375,10 +1379,27 @@ Promise<_::Forked<T>> ForkedPromise<T>::addBranch() const {
namespace _ { // private
template <typename T>
class WeakFulfiller final: public PromiseFulfiller<T> {
class WeakFulfiller final: public PromiseFulfiller<T>, private kj::Disposer {
// A wrapper around PromiseFulfiller which can be detached.
//
// There are a couple non-trivialities here:
// - If the WeakFulfiller is discarded, we want the promise it fulfills to be implicitly
// rejected.
// - We cannot destroy the WeakFulfiller until the application has discarded it *and* it has been
// detached from the underlying fulfiller, because otherwise the later detach() call will go
// to a dangling pointer. Essentially, WeakFulfiller is reference counted, although the
// refcount never goes over 2 and we manually implement the refcounting because we already need
// a mutex anyway. To this end, WeakFulfiller is its own Disposer -- dispose() is called when
// the application discards its owned pointer to the fulfiller and detach() is called when the
// promise is destroyed.
public:
WeakFulfiller(): inner(nullptr) {}
KJ_DISALLOW_COPY(WeakFulfiller);
static kj::Own<WeakFulfiller> make() {
WeakFulfiller* ptr = new WeakFulfiller;
return Own<WeakFulfiller>(ptr, *ptr);
}
void fulfill(FixVoid<T>&& value) override {
auto lock = inner.lockExclusive();
......@@ -1403,12 +1424,39 @@ public:
inner.getWithoutLock() = &newInner;
}
void detach() {
*inner.lockExclusive() = nullptr;
void detach(PromiseFulfiller<T>& from) {
auto lock = inner.lockExclusive();
if (*lock == nullptr) {
// Already disposed.
lock.release();
delete this;
} else {
KJ_IREQUIRE(*lock == &from);
*lock = nullptr;
}
}
private:
MutexGuarded<PromiseFulfiller<T>*> inner;
WeakFulfiller(): inner(nullptr) {}
void disposeImpl(void* pointer) const override {
// TODO(perf): Factor some of this out so it isn't regenerated for every fulfiller type?
auto lock = inner.lockExclusive();
if (*lock == nullptr) {
// Already detached.
lock.release();
delete this;
} else if ((*lock)->isWaiting()) {
(*lock)->reject(kj::Exception(
kj::Exception::Nature::LOCAL_BUG, kj::Exception::Durability::PERMANENT,
__FILE__, __LINE__,
kj::heapString("PromiseFulfiller was destroyed without fulfilling the promise.")));
*lock = nullptr;
}
}
};
template <typename T>
......@@ -1416,15 +1464,16 @@ class PromiseAndFulfillerAdapter {
public:
PromiseAndFulfillerAdapter(PromiseFulfiller<T>& fulfiller,
WeakFulfiller<T>& wrapper)
: wrapper(wrapper) {
: fulfiller(fulfiller), wrapper(wrapper) {
wrapper.attach(fulfiller);
}
~PromiseAndFulfillerAdapter() {
wrapper.detach();
~PromiseAndFulfillerAdapter() noexcept(false) {
wrapper.detach(fulfiller);
}
private:
PromiseFulfiller<T>& fulfiller;
WeakFulfiller<T>& wrapper;
};
......@@ -1438,7 +1487,7 @@ Promise<T> newAdaptedPromise(Params&&... adapterConstructorParams) {
template <typename T>
PromiseFulfillerPair<T> newPromiseAndFulfiller() {
auto wrapper = heap<_::WeakFulfiller<T>>();
auto wrapper = _::WeakFulfiller<T>::make();
Own<_::PromiseNode> intermediate(
heap<_::AdapterPromiseNode<_::FixVoid<T>, _::PromiseAndFulfillerAdapter<T>>>(*wrapper));
......@@ -1450,7 +1499,7 @@ PromiseFulfillerPair<T> newPromiseAndFulfiller() {
template <typename T>
PromiseFulfillerPair<T> newPromiseAndFulfiller(const EventLoop& loop) {
auto wrapper = heap<_::WeakFulfiller<T>>();
auto wrapper = _::WeakFulfiller<T>::make();
Own<_::PromiseNode> intermediate(
heap<_::AdapterPromiseNode<_::FixVoid<T>, _::PromiseAndFulfillerAdapter<T>>>(*wrapper));
......
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