Commit edbc2a5a authored by Kenton Varda's avatar Kenton Varda

IT WOOOORRRKKSS: Local capabilities, including pipelining. Now we just need an RPC protocol...

parent f566cd4d
......@@ -35,9 +35,6 @@
namespace capnp {
namespace _ { // private
Arena::~Arena() noexcept(false) {}
BuilderArena::~BuilderArena() noexcept(false) {}
namespace {
class BrokenPipeline final: public PipelineHook, public kj::Refcounted {
......@@ -110,10 +107,13 @@ kj::Own<const ClientHook> BrokenPipeline::getPipelinedCap(
} // namespace
kj::Own<const ClientHook> Arena::extractNullCap() {
return kj::refcounted<BrokenClient>("Calling null capability pointer.");
kj::Own<const ClientHook> newBrokenCap(const char* reason) {
return kj::refcounted<BrokenClient>(reason);
}
Arena::~Arena() noexcept(false) {}
BuilderArena::~BuilderArena() noexcept(false) {}
void ReadLimiter::unread(WordCount64 amount) {
// Be careful not to overflow here. Since ReadLimiter has no thread-safety, it's possible that
// the limit value was not updated correctly for one or more reads, and therefore unread() could
......@@ -180,7 +180,7 @@ void BasicReaderArena::reportReadLimitReached() {
kj::Own<const ClientHook> BasicReaderArena::extractCap(const _::StructReader& capDescriptor) {
KJ_FAIL_REQUIRE("Message contained a capability but is not imbued with a capability context.") {
return kj::heap<BrokenClient>(
return newBrokenCap(
"Calling capability extracted from message that was not imbued with a capability "
"context.");
}
......@@ -406,7 +406,7 @@ SegmentBuilder* ImbuedBuilderArena::imbue(SegmentBuilder* baseSegment) {
if (baseSegment->getSegmentId() == SegmentId(0)) {
if (segment0.getArena() == nullptr) {
kj::dtor(segment0);
kj::ctor(segment0, baseSegment);
kj::ctor(segment0, this, baseSegment);
}
result = &segment0;
} else {
......@@ -419,7 +419,7 @@ SegmentBuilder* ImbuedBuilderArena::imbue(SegmentBuilder* baseSegment) {
KJ_IF_MAYBE(segment, segmentState->get()->builders[id]) {
result = *segment;
} else {
auto newBuilder = kj::heap<ImbuedSegmentBuilder>(baseSegment);
auto newBuilder = kj::heap<ImbuedSegmentBuilder>(this, baseSegment);
result = newBuilder;
segmentState->get()->builders[id] = kj::mv(newBuilder);
}
......@@ -448,7 +448,7 @@ SegmentBuilder* ImbuedBuilderArena::getSegment(SegmentId id) {
}
BuilderArena::AllocateResult ImbuedBuilderArena::allocate(WordCount amount) {
auto result = allocate(amount);
auto result = base->allocate(amount);
result.segment = imbue(result.segment);
return result;
}
......
......@@ -57,6 +57,11 @@ class ReadLimiter;
class Segment;
typedef kj::Id<uint32_t, Segment> SegmentId;
kj::Own<const ClientHook> newBrokenCap(const char* 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.
class ReadLimiter {
// Used to keep track of how much data has been processed from a message, and cut off further
// processing if and when a particular limit is reached. This is primarily intended to guard
......@@ -167,7 +172,7 @@ private:
class ImbuedSegmentBuilder: public SegmentBuilder {
public:
inline ImbuedSegmentBuilder(SegmentBuilder* base);
inline ImbuedSegmentBuilder(ImbuedBuilderArena* arena, SegmentBuilder* base);
inline ImbuedSegmentBuilder(decltype(nullptr));
KJ_DISALLOW_COPY(ImbuedSegmentBuilder);
......@@ -188,10 +193,6 @@ public:
virtual kj::Own<const ClientHook> extractCap(const _::StructReader& capDescriptor) = 0;
// Given a StructReader for a capability descriptor embedded in the message, return the
// corresponding capability.
kj::Own<const ClientHook> extractNullCap();
// Like extractCap() but called when the pointer was null. This just returns a dummy capability
// that throws exceptions on any call.
};
class BasicReaderArena final: public Arena {
......@@ -444,8 +445,8 @@ inline BasicSegmentBuilder::BasicSegmentBuilder(
: SegmentBuilder(arena, id, ptr, readLimiter, &actualPos),
actualPos(ptr.begin()) {}
inline ImbuedSegmentBuilder::ImbuedSegmentBuilder(SegmentBuilder* base)
: SegmentBuilder(static_cast<BuilderArena*>(base->arena), base->id,
inline ImbuedSegmentBuilder::ImbuedSegmentBuilder(ImbuedBuilderArena* arena, SegmentBuilder* base)
: SegmentBuilder(arena, base->id,
kj::arrayPtr(const_cast<word*>(base->ptr.begin()), base->ptr.size()),
base->readLimiter, base->pos) {}
inline ImbuedSegmentBuilder::ImbuedSegmentBuilder(decltype(nullptr))
......
......@@ -21,6 +21,18 @@
// (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 "schema.capnp.h"
#ifdef CAPNP_CAPABILITY_H_
#error "schema.capnp should not depend on capability.h, because it contains no interfaces."
#endif
#include "test.capnp.h"
#ifndef CAPNP_CAPABILITY_H_
#error "test.capnp did not include capability.h."
#endif
#include "capability.h"
#include "test-util.h"
#include <kj/debug.h>
......@@ -36,9 +48,9 @@ public:
int& callCount;
virtual ::kj::Promise<void> foo(
::kj::Promise<void> foo(
test::TestInterface::FooParams::Reader params,
test::TestInterface::FooResults::Builder result) {
test::TestInterface::FooResults::Builder result) override {
++callCount;
EXPECT_EQ(123, params.getI());
EXPECT_TRUE(params.getJ());
......@@ -46,9 +58,9 @@ public:
return kj::READY_NOW;
}
virtual ::kj::Promise<void> bazAdvanced(
::kj::Promise<void> bazAdvanced(
::capnp::CallContext<test::TestInterface::BazParams,
test::TestInterface::BazResults> context) {
test::TestInterface::BazResults> context) override {
++callCount;
auto params = context.getParams();
checkTestMessage(params.getS());
......@@ -64,7 +76,7 @@ public:
TEST(Capability, Basic) {
kj::SimpleEventLoop loop;
int callCount;
int callCount = 0;
test::TestInterface::Client client(makeLocalClient(kj::heap<TestInterfaceImpl>(callCount), loop));
auto request1 = client.fooRequest();
......@@ -103,9 +115,9 @@ public:
int& callCount;
virtual ::kj::Promise<void> foo(
::kj::Promise<void> foo(
test::TestInterface::FooParams::Reader params,
test::TestInterface::FooResults::Builder result) {
test::TestInterface::FooResults::Builder result) override {
++callCount;
EXPECT_EQ(321, params.getI());
EXPECT_FALSE(params.getJ());
......@@ -113,8 +125,8 @@ public:
return kj::READY_NOW;
}
virtual ::kj::Promise<void> graultAdvanced(
::capnp::CallContext<test::TestExtends::GraultParams, test::TestAllTypes> context) {
::kj::Promise<void> graultAdvanced(
::capnp::CallContext<test::TestExtends::GraultParams, test::TestAllTypes> context) override {
++callCount;
context.releaseParams();
......@@ -127,7 +139,7 @@ public:
TEST(Capability, Inheritance) {
kj::SimpleEventLoop loop;
int callCount;
int callCount = 0;
test::TestExtends::Client client(makeLocalClient(kj::heap<TestExtendsImpl>(callCount), loop));
auto request1 = client.fooRequest();
......@@ -150,6 +162,67 @@ TEST(Capability, Inheritance) {
EXPECT_EQ(2, callCount);
}
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("foo", params.getS());
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.setN(234);
result.setOutCap(test::TestExtends::Client(
makeLocalClient(kj::heap<TestExtendsImpl>(callCount))));
});
}
};
TEST(Capability, Pipelining) {
kj::SimpleEventLoop loop;
int callCount = 0;
int chainedCallCount = 0;
test::TestPipeline::Client client(makeLocalClient(kj::heap<TestPipelineImpl>(callCount), loop));
auto request = client.getCapRequest();
request.setS("foo");
request.setInCap(test::TestInterface::Client(
makeLocalClient(kj::heap<TestInterfaceImpl>(chainedCallCount), loop)));
auto promise = request.send();
auto pipelineRequest = promise.getOutCap().fooRequest();
pipelineRequest.setI(321);
auto pipelinePromise = pipelineRequest.send();
EXPECT_EQ(0, callCount);
EXPECT_EQ(0, chainedCallCount);
auto response = loop.wait(kj::mv(pipelinePromise));
EXPECT_EQ("bar", response.getX());
EXPECT_EQ(2, callCount);
EXPECT_EQ(1, chainedCallCount);
}
} // namespace
} // namespace _
} // namespace capnp
This diff is collapsed.
......@@ -26,6 +26,7 @@
#include <kj/async.h>
#include "object.h"
#include "pointer-helpers.h"
namespace capnp {
......@@ -133,6 +134,9 @@ public:
private:
kj::Own<const ClientHook> hook;
template <typename, ::capnp::Kind>
friend struct ::capnp::_::PointerHelpers;
protected:
Client() = default;
......@@ -148,7 +152,7 @@ class CallContextHook;
template <typename Params, typename Results>
class CallContext: public kj::DisallowConstCopy {
// Wrapper around TypelessCallContext with a specific return type.
// Wrapper around CallContextHook with a specific return type.
//
// Methods of this class may only be called from within the server's event loop, not from other
// threads.
......@@ -246,51 +250,6 @@ kj::Own<const ClientHook> makeLocalClient(kj::Own<Capability::Server>&& server,
//
// TODO(now): Templated version or something.
// =======================================================================================
struct PipelineOp {
enum Type {
GET_POINTER_FIELD
// There may be other types in the future...
};
Type type;
union {
uint16_t pointerIndex; // for GET_POINTER_FIELD
};
};
struct TypelessResults {
// Result of a call, before it has been type-wrapped. Designed to be used as
// RemotePromise<CallResult>.
typedef ObjectPointer::Reader Reader;
// So RemotePromise<CallResult> resolves to Own<ObjectPointer::Reader>.
class Pipeline {
public:
inline explicit Pipeline(kj::Own<const PipelineHook>&& hook): hook(kj::mv(hook)) {}
Pipeline getPointerField(uint16_t pointerIndex) const;
// Return a new Promise representing a sub-object of the result. `pointerIndex` is the index
// of the sub-object within the pointer section of the result (the result must be a struct).
//
// TODO(kenton): On GCC 4.8 / Clang 3.3, use rvalue qualifiers to avoid the need for copies.
// Also make `ops` into a Vector to optimize this.
Capability::Client asCap() const;
// Expect that the result is a capability and construct a pipelined version of it now.
private:
kj::Own<const PipelineHook> hook;
kj::Array<PipelineOp> ops;
inline Pipeline(kj::Own<const PipelineHook>&& hook, kj::Array<PipelineOp>&& ops)
: hook(kj::mv(hook)), ops(kj::mv(ops)) {}
};
};
// =======================================================================================
// Hook interfaces which must be implemented by the RPC system. Applications never call these
// directly; the RPC system implements them and the types defined earlier in this file wrap them.
......@@ -299,7 +258,7 @@ class RequestHook {
// Hook interface implemented by RPC system representing a request being built.
public:
virtual RemotePromise<TypelessResults> send() = 0;
virtual RemotePromise<ObjectPointer> send() = 0;
// Send the call and return a promise for the result.
};
......@@ -314,26 +273,11 @@ public:
// Just here to make sure the type is dynamic.
};
class PipelineHook {
// Represents a currently-running call, and implements pipelined requests on its result.
public:
virtual kj::Own<const PipelineHook> addRef() const = 0;
// Increment this object's reference count.
virtual kj::Own<const ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) const = 0;
// Extract a promised Capability from the results.
virtual kj::Own<const ClientHook> getPipelinedCap(kj::Array<PipelineOp>&& ops) const {
// Version of getPipelinedCap() passing the array by move. May avoid a copy in some cases.
// Default implementation just calls the other version.
return getPipelinedCap(ops.asPtr());
}
};
// class PipelineHook is declared in object.h because it is needed there.
class ClientHook {
public:
virtual Request<ObjectPointer, TypelessResults> newCall(
virtual Request<ObjectPointer, ObjectPointer> newCall(
uint64_t interfaceId, uint16_t methodId, uint firstSegmentWordSize) const = 0;
// Start a new call, allowing the client to allocate request/response objects as it sees fit.
// This version is used when calls are made from application code in the local process.
......@@ -386,6 +330,35 @@ public:
virtual kj::Own<CallContextHook> addRef() = 0;
};
kj::Own<const ClientHook> newBrokenCap(const char* reason);
// Helper function that creates a capability which simply throws exceptions when called.
// =======================================================================================
// Extend PointerHelpers for interfaces
namespace _ { // private
template <typename T>
struct PointerHelpers<T, Kind::INTERFACE> {
static inline typename T::Client get(PointerReader reader) {
return typename T::Client(reader.getCapability());
}
static inline typename T::Client get(PointerBuilder builder) {
return typename T::Client(builder.getCapability());
}
static inline void set(PointerBuilder builder, typename T::Client&& value) {
builder.setCapability(kj::mv(value.Capability::Client::hook));
}
static inline void adopt(PointerBuilder builder, Orphan<T>&& value) {
builder.adopt(kj::mv(value.builder));
}
static inline Orphan<T> disown(PointerBuilder builder) {
return Orphan<T>(builder.disown());
}
};
} // namespace _ (private)
// =======================================================================================
// Inline implementation details
......@@ -396,22 +369,18 @@ RemotePromise<Results> Request<Params, Results>::send() {
// Convert the Promise to return the correct response type.
// Explicitly upcast to kj::Promise to make clear that calling .then() doesn't invalidate the
// Pipeline part of the RemotePromise.
auto typedPromise = kj::implicitCast<kj::Promise<Response<TypelessResults>>&>(typelessPromise)
.thenInAnyThread([](Response<TypelessResults>&& response) -> Response<Results> {
auto typedPromise = kj::implicitCast<kj::Promise<Response<ObjectPointer>>&>(typelessPromise)
.thenInAnyThread([](Response<ObjectPointer>&& response) -> Response<Results> {
return Response<Results>(response.getAs<Results>(), kj::mv(response.hook));
});
// Wrap the typeless pipeline in a typed wrapper.
typename Results::Pipeline typedPipeline(
kj::mv(kj::implicitCast<TypelessResults::Pipeline&>(typelessPromise)));
kj::mv(kj::implicitCast<ObjectPointer::Pipeline&>(typelessPromise)));
return RemotePromise<Results>(kj::mv(typedPromise), kj::mv(typedPipeline));
}
inline Capability::Client TypelessResults::Pipeline::asCap() const {
return Capability::Client(hook->getPipelinedCap(ops));
}
inline Capability::Client::Client(kj::Own<const ClientHook>&& hook): hook(kj::mv(hook)) {}
inline Capability::Client::Client(const Client& other): hook(other.hook->addRef()) {}
inline Capability::Client& Capability::Client::operator=(const Client& other) {
......
......@@ -110,12 +110,14 @@ template <typename T, Kind k> struct Kind_<List<T, k>> { static constexpr Kind k
template <typename T, Kind k = kind<T>()> struct ReaderFor_ { typedef typename T::Reader Type; };
template <typename T> struct ReaderFor_<T, Kind::PRIMITIVE> { typedef T Type; };
template <typename T> struct ReaderFor_<T, Kind::ENUM> { typedef T Type; };
template <typename T> struct ReaderFor_<T, Kind::INTERFACE> { typedef typename T::Client Type; };
template <typename T> using ReaderFor = typename ReaderFor_<T>::Type;
// The type returned by List<T>::Reader::operator[].
template <typename T, Kind k = kind<T>()> struct BuilderFor_ { typedef typename T::Builder Type; };
template <typename T> struct BuilderFor_<T, Kind::PRIMITIVE> { typedef T Type; };
template <typename T> struct BuilderFor_<T, Kind::ENUM> { typedef T Type; };
template <typename T> struct BuilderFor_<T, Kind::INTERFACE> { typedef typename T::Client Type; };
template <typename T> using BuilderFor = typename BuilderFor_<T>::Type;
// The type returned by List<T>::Builder::operator[].
......
......@@ -571,7 +571,8 @@ private:
" inline ", titleCase, "::Builder init", titleCase, "();\n"
"\n"),
kj::strTree(),
proto.hasDiscriminantValue() ? kj::strTree() :
kj::strTree(" inline ", titleCase, "::Pipeline get", titleCase, "() const;\n"),
kj::strTree(
kj::mv(unionDiscrim.isDefs),
......@@ -620,7 +621,11 @@ private:
"inline ", scope, titleCase, "::Builder ", scope, "Builder::get", titleCase, "() {\n",
unionDiscrim.check,
" return ", scope, titleCase, "::Builder(_builder);\n"
"}\n"
"}\n",
proto.hasDiscriminantValue() ? kj::strTree() : kj::strTree(
"inline ", scope, titleCase, "::Pipeline ", scope, "Pipeline::get", titleCase, "() const {\n",
" return ", scope, titleCase, "::Pipeline(_typeless.noop());\n"
"}\n"),
"inline ", scope, titleCase, "::Builder ", scope, "Builder::init", titleCase, "() {\n",
unionDiscrim.set,
KJ_MAP(slot, slots) {
......@@ -810,8 +815,68 @@ private:
};
} else if (kind == FieldKind::INTERFACE) {
// Not implemented.
return FieldText { kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree() };
return FieldText {
kj::strTree(
kj::mv(unionDiscrim.readerIsDecl),
" inline bool has", titleCase, "() const;\n"
" inline ", type, "::Client get", titleCase, "() const;\n"
"\n"),
kj::strTree(
kj::mv(unionDiscrim.builderIsDecl),
" inline bool has", titleCase, "();\n"
" inline ", type, "::Client get", titleCase, "();\n"
" inline void set", titleCase, "(", type, "::Client&& value);\n",
" inline void adopt", titleCase, "(::capnp::Orphan<", type, ">&& value);\n"
" inline ::capnp::Orphan<", type, "> disown", titleCase, "();\n"
"\n"),
kj::strTree(
proto.hasDiscriminantValue() ? kj::strTree() : kj::strTree(
" inline ", type, "::Client get", titleCase, "() const;\n")),
kj::strTree(
kj::mv(unionDiscrim.isDefs),
"inline bool ", scope, "Reader::has", titleCase, "() const {\n",
unionDiscrim.has,
" return !_reader.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n"
"}\n"
"inline bool ", scope, "Builder::has", titleCase, "() {\n",
unionDiscrim.has,
" return !_builder.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n"
"}\n"
"inline ", type, "::Client ", scope, "Reader::get", titleCase, "() const {\n",
unionDiscrim.check,
" return ::capnp::_::PointerHelpers<", type, ">::get(\n"
" _reader.getPointerField(", offset, " * ::capnp::POINTERS));\n"
"}\n"
"inline ", type, "::Client ", scope, "Builder::get", titleCase, "() {\n",
unionDiscrim.check,
" return ::capnp::_::PointerHelpers<", type, ">::get(\n"
" _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n"
"}\n",
proto.hasDiscriminantValue() ? kj::strTree() : kj::strTree(
"inline ", type, "::Client ", scope, "Pipeline::get", titleCase, "() const {\n",
" return ", type, "::Client(_typeless.getPointerField(", offset, ").asCap());\n"
"}\n"),
"inline void ", scope, "Builder::set", titleCase, "(", type, "::Client&& cap) {\n",
unionDiscrim.set,
" ::capnp::_::PointerHelpers<", type, ">::set(\n"
" _builder.getPointerField(", offset, " * ::capnp::POINTERS), kj::mv(cap));\n"
"}\n",
"inline void ", scope, "Builder::adopt", titleCase, "(\n"
" ::capnp::Orphan<", type, ">&& value) {\n",
unionDiscrim.set,
" ::capnp::_::PointerHelpers<", type, ">::adopt(\n"
" _builder.getPointerField(", offset, " * ::capnp::POINTERS), kj::mv(value));\n"
"}\n"
"inline ::capnp::Orphan<", type, "> ", scope, "Builder::disown", titleCase, "() {\n",
unionDiscrim.check,
" return ::capnp::_::PointerHelpers<", type, ">::disown(\n"
" _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n"
"}\n"
"\n")
};
} else if (kind == FieldKind::OBJECT) {
return FieldText {
......@@ -932,7 +997,11 @@ private:
" inline ::capnp::Orphan<", type, "> disown", titleCase, "();\n"
"\n"),
kj::strTree(),
kj::strTree(
kind == FieldKind::STRUCT && !proto.hasDiscriminantValue()
? kj::strTree(
" inline ", type, "::Pipeline get", titleCase, "() const;\n")
: kj::strTree()),
kj::strTree(
kj::mv(unionDiscrim.isDefs),
......@@ -953,7 +1022,13 @@ private:
unionDiscrim.check,
" return ::capnp::_::PointerHelpers<", type, ">::get(\n"
" _builder.getPointerField(", offset, " * ::capnp::POINTERS)", defaultParam, ");\n"
"}\n"
"}\n",
kind == FieldKind::STRUCT && !proto.hasDiscriminantValue()
? kj::strTree(
"inline ", type, "::Pipeline ", scope, "Pipeline::get", titleCase, "() const {\n",
" return ", type, "::Pipeline(_typeless.getPointerField(", offset, "));\n"
"}\n")
: kj::strTree(),
"inline void ", scope, "Builder::set", titleCase, "(", type, "::Reader value) {\n",
unionDiscrim.set,
" ::capnp::_::PointerHelpers<", type, ">::set(\n"
......@@ -1079,12 +1154,12 @@ private:
"public:\n"
" typedef ", unqualifiedParentType, " Pipelines;\n"
"\n"
" inline explicit Pipeline(::capnp::TypelessResults::Pipeline&& typeless)\n"
" inline explicit Pipeline(::capnp::ObjectPointer::Pipeline&& typeless)\n"
" : _typeless(kj::mv(typeless)) {}\n"
"\n",
kj::mv(methodDecls),
"private:\n"
" ::capnp::TypelessResults::Pipeline _typeless;\n"
" ::capnp::ObjectPointer::Pipeline _typeless;\n"
" template <typename T, ::capnp::Kind k>\n"
" friend struct ::capnp::ToDynamic_;\n"
"};\n"
......@@ -1263,7 +1338,7 @@ private:
return kj::strTree(",\n public virtual ", e.typeName, "::Client");
}, " {\n"
"public:\n"
" inline Client(::kj::Own<const ::capnp::ClientHook>&& hook)\n"
" inline explicit Client(::kj::Own<const ::capnp::ClientHook>&& hook)\n"
" : ::capnp::Capability::Client(::kj::mv(hook)) {}\n"
"\n",
KJ_MAP(m, methods) { return kj::mv(m.clientDecls); },
......
This diff is collapsed.
......@@ -19,6 +19,7 @@ struct Token {
class Reader;
class Builder;
class Pipeline;
enum Which: uint16_t {
IDENTIFIER,
STRING_LITERAL,
......@@ -35,6 +36,7 @@ struct Statement {
class Reader;
class Builder;
class Pipeline;
enum Which: uint16_t {
LINE,
BLOCK,
......@@ -46,6 +48,7 @@ struct LexedTokens {
class Reader;
class Builder;
class Pipeline;
};
struct LexedStatements {
......@@ -53,6 +56,7 @@ struct LexedStatements {
class Reader;
class Builder;
class Pipeline;
};
} // namespace
......@@ -241,6 +245,19 @@ inline ::kj::StringTree KJ_STRINGIFY(Token::Builder builder) {
return ::capnp::_::structString<Token>(builder._builder.asReader());
}
class Token::Pipeline {
public:
typedef Token Pipelines;
inline explicit Pipeline(::capnp::ObjectPointer::Pipeline&& typeless)
: _typeless(kj::mv(typeless)) {}
private:
::capnp::ObjectPointer::Pipeline _typeless;
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
};
class Statement::Reader {
public:
typedef Statement Reads;
......@@ -351,6 +368,19 @@ inline ::kj::StringTree KJ_STRINGIFY(Statement::Builder builder) {
return ::capnp::_::structString<Statement>(builder._builder.asReader());
}
class Statement::Pipeline {
public:
typedef Statement Pipelines;
inline explicit Pipeline(::capnp::ObjectPointer::Pipeline&& typeless)
: _typeless(kj::mv(typeless)) {}
private:
::capnp::ObjectPointer::Pipeline _typeless;
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
};
class LexedTokens::Reader {
public:
typedef LexedTokens Reads;
......@@ -414,6 +444,19 @@ inline ::kj::StringTree KJ_STRINGIFY(LexedTokens::Builder builder) {
return ::capnp::_::structString<LexedTokens>(builder._builder.asReader());
}
class LexedTokens::Pipeline {
public:
typedef LexedTokens Pipelines;
inline explicit Pipeline(::capnp::ObjectPointer::Pipeline&& typeless)
: _typeless(kj::mv(typeless)) {}
private:
::capnp::ObjectPointer::Pipeline _typeless;
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
};
class LexedStatements::Reader {
public:
typedef LexedStatements Reads;
......@@ -477,6 +520,19 @@ inline ::kj::StringTree KJ_STRINGIFY(LexedStatements::Builder builder) {
return ::capnp::_::structString<LexedStatements>(builder._builder.asReader());
}
class LexedStatements::Pipeline {
public:
typedef LexedStatements Pipelines;
inline explicit Pipeline(::capnp::ObjectPointer::Pipeline&& typeless)
: _typeless(kj::mv(typeless)) {}
private:
::capnp::ObjectPointer::Pipeline _typeless;
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
};
// =======================================================================================
inline Token::Which Token::Reader::which() const {
......
......@@ -1798,7 +1798,7 @@ struct WireHelpers {
static KJ_ALWAYS_INLINE(kj::Own<const ClientHook> readCapabilityPointer(
SegmentReader* segment, const WirePointer* ref, const word* refTarget, int nestingLimit)) {
if (ref->isNull()) {
return segment->getArena()->extractNullCap();
return newBrokenCap("Calling null capability pointer.");
} else {
return segment->getArena()->extractCap(readStructOrCapDescPointer(
WirePointer::CAPABILITY, segment, ref, refTarget, nullptr, nestingLimit));
......
......@@ -26,6 +26,10 @@
namespace capnp {
kj::Own<const ClientHook> PipelineHook::getPipelinedCap(kj::Array<PipelineOp>&& ops) const {
return getPipelinedCap(ops.asPtr());
}
kj::Own<const ClientHook> ObjectPointer::Reader::getPipelinedCap(
kj::ArrayPtr<const PipelineOp> ops) const {
_::PointerReader pointer = reader;
......@@ -41,4 +45,29 @@ kj::Own<const ClientHook> ObjectPointer::Reader::getPipelinedCap(
return pointer.getCapability();
}
ObjectPointer::Pipeline ObjectPointer::Pipeline::noop() const {
auto newOps = kj::heapArray<PipelineOp>(ops.size());
for (auto i: kj::indices(ops)) {
newOps[i] = ops[i];
}
return Pipeline(hook->addRef(), kj::mv(newOps));
}
ObjectPointer::Pipeline ObjectPointer::Pipeline::getPointerField(
uint16_t pointerIndex) const {
auto newOps = kj::heapArray<PipelineOp>(ops.size() + 1);
for (auto i: kj::indices(ops)) {
newOps[i] = ops[i];
}
auto& newOp = newOps[ops.size()];
newOp.type = PipelineOp::GET_POINTER_FIELD;
newOp.pointerIndex = pointerIndex;
return Pipeline(hook->addRef(), kj::mv(newOps));
}
kj::Own<const ClientHook> ObjectPointer::Pipeline::asCap() const {
return hook->getPipelinedCap(ops);
}
} // namespace capnp
......@@ -33,8 +33,47 @@ namespace capnp {
class StructSchema;
class ListSchema;
class Orphanage;
struct PipelineOp;
class ClientHook;
class PipelineHook;
// =======================================================================================
// Pipeline helpers
//
// These relate to capabilities, but we don't declare them in capability.h because generated code
// for structs needs to know about these, even in files that contain no interfaces.
struct PipelineOp {
// Corresponds to rpc.capnp's PromisedAnswer.Op.
enum Type {
GET_POINTER_FIELD
// There may be other types in the future...
};
Type type;
union {
uint16_t pointerIndex; // for GET_POINTER_FIELD
};
};
class PipelineHook {
// Represents a currently-running call, and implements pipelined requests on its result.
public:
virtual kj::Own<const PipelineHook> addRef() const = 0;
// Increment this object's reference count.
virtual kj::Own<const ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) const = 0;
// Extract a promised Capability from the results.
virtual kj::Own<const ClientHook> getPipelinedCap(kj::Array<PipelineOp>&& ops) const;
// Version of getPipelinedCap() passing the array by move. May avoid a copy in some cases.
// Default implementation just calls the other version.
};
// =======================================================================================
// ObjectPointer!
struct ObjectPointer {
// Reader/Builder for the `Object` field type, i.e. a pointer that can point to an arbitrary
......@@ -152,8 +191,36 @@ struct ObjectPointer {
_::PointerBuilder builder;
friend class CapBuilderContext;
};
class Pipeline {
public:
inline explicit Pipeline(kj::Own<const PipelineHook>&& hook): hook(kj::mv(hook)) {}
Pipeline noop() const;
// Just make a copy.
Pipeline getPointerField(uint16_t pointerIndex) const;
// Return a new Promise representing a sub-object of the result. `pointerIndex` is the index
// of the sub-object within the pointer section of the result (the result must be a struct).
//
// TODO(kenton): On GCC 4.8 / Clang 3.3, use rvalue qualifiers to avoid the need for copies.
// Also make `ops` into a Vector to optimize this.
kj::Own<const ClientHook> asCap() const;
// Expect that the result is a capability and construct a pipelined version of it now.
private:
kj::Own<const PipelineHook> hook;
kj::Array<PipelineOp> ops;
inline Pipeline(kj::Own<const PipelineHook>&& hook, kj::Array<PipelineOp>&& ops)
: hook(kj::mv(hook)), ops(kj::mv(ops)) {}
};
};
// TODO(now): delete
typedef ObjectPointer TypelessResults;
template <>
class Orphan<ObjectPointer> {
// An orphaned object of unknown type.
......
......@@ -52,14 +52,14 @@ public:
Orphan(Orphan&&) = default;
Orphan& operator=(Orphan&&) = default;
inline typename T::Builder get();
inline BuilderFor<T> get();
// Get the underlying builder. If the orphan is null, this will allocate and return a default
// object rather than crash. This is done for security -- otherwise, you might enable a DoS
// attack any time you disown a field and fail to check if it is null. In the case of structs,
// this means that the orphan is no longer null after get() returns. In the case of lists,
// no actual object is allocated since a simple empty ListBuilder can be returned.
inline typename T::Reader getReader() const;
inline ReaderFor<T> getReader() const;
inline bool operator==(decltype(nullptr)) const { return builder == nullptr; }
inline bool operator!=(decltype(nullptr)) const { return builder != nullptr; }
......@@ -198,12 +198,12 @@ struct OrphanGetImpl<Data, Kind::BLOB> {
} // namespace _ (private)
template <typename T>
inline typename T::Builder Orphan<T>::get() {
inline BuilderFor<T> Orphan<T>::get() {
return _::OrphanGetImpl<T>::apply(builder);
}
template <typename T>
inline typename T::Reader Orphan<T>::getReader() const {
inline ReaderFor<T> Orphan<T>::getReader() const {
return _::OrphanGetImpl<T>::applyReader(builder);
}
......
This diff is collapsed.
......@@ -584,3 +584,7 @@ interface TestExtends extends(TestInterface) {
corge @1 TestAllTypes -> ();
grault @2 () -> TestAllTypes;
}
interface TestPipeline {
getCap @0 (s: Text, inCap :TestInterface) -> (n :UInt32, outCap :TestExtends);
}
......@@ -118,7 +118,9 @@ void EventLoop::waitImpl(Own<_::PromiseNode> node, _::ExceptionOrValue& result)
EventLoop::Event::~Event() noexcept(false) {
if (this != &loop.queue) {
KJ_ASSERT(next == nullptr || std::uncaught_exception(), "Event destroyed while armed.");
KJ_ASSERT(next == nullptr || std::uncaught_exception(),
"Event destroyed while armed. You must call disarm() in the subclass's destructor "
"in order to ensure that fire() is not running when the event is destroyed.");
}
}
......@@ -393,7 +395,9 @@ ForkHubBase::ForkHubBase(const EventLoop& loop, Own<PromiseNode>&& inner,
arm(YIELD);
}
ForkHubBase::~ForkHubBase() noexcept(false) {}
ForkHubBase::~ForkHubBase() noexcept(false) {
disarm();
}
void ForkHubBase::fire() {
if (!isWaiting && !inner->onReady(*this)) {
......
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