Commit d07bf3b8 authored by Kenton Varda's avatar Kenton Varda

More capabilities core API WIP.

parent 398dac0d
......@@ -24,10 +24,13 @@
#define CAPNP_PRIVATE
#include "arena.h"
#include "message.h"
#include "capability.h"
#include <kj/debug.h>
#include <vector>
#include <string.h>
#include <stdio.h>
#include "capability.h"
#include "capability-context.h"
namespace capnp {
namespace _ { // private
......@@ -99,10 +102,42 @@ void BasicReaderArena::reportReadLimitReached() {
}
}
namespace {
class DummyClientHook final: public ClientHook {
public:
Request<ObjectPointer, TypelessAnswer> newCall(
uint64_t interfaceId, uint16_t methodId) const override {
KJ_FAIL_REQUIRE("Calling capability that was extracted from a message that had no "
"capability context.");
}
kj::Promise<void> whenResolved() const override {
return kj::READY_NOW;
}
kj::Own<ClientHook> addRef() const override {
return kj::heap<DummyClientHook>();
}
void* getBrand() const override {
return nullptr;
}
};
} // namespace
kj::Own<ClientHook> BasicReaderArena::extractCap(const _::StructReader& capDescriptor) {
KJ_FAIL_REQUIRE("Message contained a capability but is not imbued with a capability context.") {
return kj::heap<DummyClientHook>();
}
}
// =======================================================================================
ImbuedReaderArena::ImbuedReaderArena(Arena* base)
: base(base), segment0(nullptr, SegmentId(0), nullptr, nullptr) {}
ImbuedReaderArena::ImbuedReaderArena(Arena* base, CapExtractorBase* capExtractor)
: base(base), capExtractor(capExtractor),
segment0(nullptr, SegmentId(0), nullptr, nullptr) {}
ImbuedReaderArena::~ImbuedReaderArena() noexcept(false) {}
SegmentReader* ImbuedReaderArena::imbue(SegmentReader* baseSegment) {
......@@ -139,8 +174,6 @@ SegmentReader* ImbuedReaderArena::imbue(SegmentReader* baseSegment) {
return result;
}
// implements Arena ------------------------------------------------
SegmentReader* ImbuedReaderArena::tryGetSegment(SegmentId id) {
return imbue(base->tryGetSegment(id));
}
......@@ -149,6 +182,10 @@ void ImbuedReaderArena::reportReadLimitReached() {
return base->reportReadLimitReached();
}
kj::Own<ClientHook> ImbuedReaderArena::extractCap(const _::StructReader& capDescriptor) {
return capExtractor->extractCapInternal(capDescriptor);
}
// =======================================================================================
BasicBuilderArena::BasicBuilderArena(MessageBuilder* message)
......@@ -288,10 +325,19 @@ void BasicBuilderArena::reportReadLimitReached() {
}
}
kj::Own<ClientHook> BasicBuilderArena::extractCap(const _::StructReader& capDescriptor) {
KJ_FAIL_REQUIRE("Message contains no capabilities.");
}
void BasicBuilderArena::injectCap(_::PointerBuilder pointer, kj::Own<ClientHook>&& cap) {
KJ_FAIL_REQUIRE("Cannot inject capability into a builder that has not been imbued with a "
"capability context.");
}
// =======================================================================================
ImbuedBuilderArena::ImbuedBuilderArena(BuilderArena* base)
: base(base), segment0(nullptr) {}
ImbuedBuilderArena::ImbuedBuilderArena(BuilderArena* base, CapInjectorBase* capInjector)
: base(base), capInjector(capInjector), segment0(nullptr) {}
ImbuedBuilderArena::~ImbuedBuilderArena() noexcept(false) {}
SegmentBuilder* ImbuedBuilderArena::imbue(SegmentBuilder* baseSegment) {
......@@ -334,6 +380,10 @@ void ImbuedBuilderArena::reportReadLimitReached() {
base->reportReadLimitReached();
}
kj::Own<ClientHook> ImbuedBuilderArena::extractCap(const _::StructReader& capDescriptor) {
return capInjector->getInjectedCapInternal(capDescriptor);
}
SegmentBuilder* ImbuedBuilderArena::getSegment(SegmentId id) {
return imbue(base->getSegment(id));
}
......@@ -344,5 +394,9 @@ BuilderArena::AllocateResult ImbuedBuilderArena::allocate(WordCount amount) {
return result;
}
void ImbuedBuilderArena::injectCap(_::PointerBuilder pointer, kj::Own<ClientHook>&& cap) {
return capInjector->injectCapInternal(pointer, kj::mv(cap));
}
} // namespace _ (private)
} // namespace capnp
......@@ -34,10 +34,13 @@
#include <kj/mutex.h>
#include "common.h"
#include "message.h"
#include "layout.h"
namespace capnp {
class TypelessCapability;
class CapExtractorBase;
class CapInjectorBase;
class ClientHook;
namespace _ { // private
......@@ -182,7 +185,7 @@ public:
// the VALIDATE_INPUT() macro which may throw an exception; if it return normally, the caller
// will need to continue with default values.
virtual kj::Own<TypelessCapability> extractCap(const _::StructReader& capDescriptor);
virtual kj::Own<ClientHook> extractCap(const _::StructReader& capDescriptor) = 0;
// Given a StructReader for a capability descriptor embedded in the message, return the
// corresponding capability.
};
......@@ -196,6 +199,7 @@ public:
// implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override;
kj::Own<ClientHook> extractCap(const _::StructReader& capDescriptor);
private:
MessageReader* message;
......@@ -210,7 +214,7 @@ private:
class ImbuedReaderArena final: public Arena {
public:
ImbuedReaderArena(Arena* base);
ImbuedReaderArena(Arena* base, CapExtractorBase* capExtractor);
~ImbuedReaderArena() noexcept(false);
KJ_DISALLOW_COPY(ImbuedReaderArena);
......@@ -219,9 +223,11 @@ public:
// implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override;
kj::Own<ClientHook> extractCap(const _::StructReader& capDescriptor);
private:
Arena* base;
CapExtractorBase* capExtractor;
// Optimize for single-segment messages so that small messages are handled quickly.
SegmentReader segment0;
......@@ -248,7 +254,7 @@ public:
// the arena is guaranteed to succeed. Therefore callers should try to allocate from a specific
// segment first if there is one, then fall back to the arena.
virtual void injectCap(_::PointerBuilder pointer, kj::Own<TypelessCapability>&& cap) = 0;
virtual void injectCap(_::PointerBuilder pointer, kj::Own<ClientHook>&& cap) = 0;
// Add the capability to the message and initialize the given pointer as an interface pointer
// pointing to this cap.
};
......@@ -271,10 +277,12 @@ public:
// implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override;
kj::Own<ClientHook> extractCap(const _::StructReader& capDescriptor);
// implements BuilderArena -----------------------------------------
SegmentBuilder* getSegment(SegmentId id) override;
AllocateResult allocate(WordCount amount) override;
void injectCap(_::PointerBuilder pointer, kj::Own<ClientHook>&& cap);
private:
MessageBuilder* message;
......@@ -294,7 +302,7 @@ class ImbuedBuilderArena final: public BuilderArena {
// A BuilderArena imbued with the ability to inject capabilities.
public:
ImbuedBuilderArena(BuilderArena* base);
ImbuedBuilderArena(BuilderArena* base, CapInjectorBase* capInjector);
~ImbuedBuilderArena() noexcept(false);
KJ_DISALLOW_COPY(ImbuedBuilderArena);
......@@ -304,13 +312,16 @@ public:
// implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override;
kj::Own<ClientHook> extractCap(const _::StructReader& capDescriptor);
// implements BuilderArena -----------------------------------------
SegmentBuilder* getSegment(SegmentId id) override;
AllocateResult allocate(WordCount amount) override;
void injectCap(_::PointerBuilder pointer, kj::Own<ClientHook>&& cap);
private:
BuilderArena* base;
CapInjectorBase* capInjector;
ImbuedSegmentBuilder segment0;
......
// 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.
#define CAPNP_PRIVATE
#include "capability-context.h"
#include "arena.h"
#include <kj/debug.h>
namespace capnp {
CapReaderContext::CapReaderContext(CapExtractorBase& extractor): extractor(&extractor) {}
CapReaderContext::~CapReaderContext() noexcept(false) {
if (extractor == nullptr) {
kj::dtor(arena());
}
}
ObjectPointer::Reader CapReaderContext::imbue(ObjectPointer::Reader base) {
KJ_REQUIRE(extractor != nullptr, "imbue() can only be called once.");
KJ_IF_MAYBE(oldArena, base.reader.getArena()) {
kj::ctor(arena(), oldArena, extractor);
} else {
KJ_FAIL_REQUIRE("Cannot imbue unchecked message.");
}
extractor = nullptr;
return ObjectPointer::Reader(base.reader.imbue(arena()));
}
CapBuilderContext::CapBuilderContext(CapInjectorBase& injector): injector(&injector) {}
CapBuilderContext::~CapBuilderContext() noexcept(false) {
if (injector == nullptr) {
kj::dtor(arena());
}
}
ObjectPointer::Builder CapBuilderContext::imbue(ObjectPointer::Builder base) {
KJ_REQUIRE(injector != nullptr, "imbue() can only be called once.");
kj::ctor(arena(), &base.builder.getArena(), injector);
injector = nullptr;
return ObjectPointer::Builder(base.builder.imbue(arena()));
}
} // namespace capnp
// 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.
// Classes for imbuing message readers/builders with a capability context.
//
// These classes are for use by RPC implementations. Application code need not know about them.
//
// Normally, MessageReader and MessageBuilder do not support interface pointers because they
// are not RPC-aware and so have no idea how to convert between a serialized CapabilityDescriptor
// and a live capability. To fix this, a reader/builder object needs to be "imbued" with a
// capability context. This creates a new reader/builder which points at the same object but has
// the ability to deal with interface fields. Use `CapReaderContext` and `CapBuilderContext` to
// accomplish this.
#ifndef CAPNP_CAPABILITY_CONTEXT_H_
#define CAPNP_CAPABILITY_CONTEXT_H_
#include "layout.h"
#include "object.h"
#include <kj/mutex.h>
namespace capnp {
class ClientHook;
namespace _ { // private
class ImbuedReaderArena;
class ImbuedBuilderArena;
} // namespace _ (private)
class CapExtractorBase {
// Non-template base class for CapExtractor<T>.
private:
virtual kj::Own<ClientHook> extractCapInternal(const _::StructReader& capDescriptor) = 0;
friend class _::ImbuedReaderArena;
};
class CapInjectorBase {
// Non-template base class for CapInjector<T>.
private:
virtual void injectCapInternal(_::PointerBuilder builder, kj::Own<ClientHook>&& cap) = 0;
virtual kj::Own<ClientHook> getInjectedCapInternal(const _::StructReader& capDescriptor) = 0;
friend class _::ImbuedBuilderArena;
};
template <typename CapDescriptor>
class CapExtractor: public CapExtractorBase {
// Callback used to read a capability from a message, implemented by the RPC system.
// `CapDescriptor` is the struct type which the RPC implementation uses to represent
// capabilities. (On the wire, an interface pointer actually points to a struct of this type.)
public:
virtual kj::Own<ClientHook> extractCap(typename CapDescriptor::Reader descriptor) = 0;
// Given the descriptor read off the wire, construct a live capability.
private:
kj::Own<ClientHook> extractCapInternal(const _::StructReader& capDescriptor) override final {
return extractCap(typename CapDescriptor::Reader(capDescriptor));
}
};
template <typename CapDescriptor>
class CapInjector: public CapInjectorBase {
// Callback used to write a capability into a message, implemented by the RPC system.
// `CapDescriptor` is the struct type which the RPC implementation uses to represent
// capabilities. (On the wire, an interface pointer actually points to a struct of this type.)
public:
virtual void injectCap(typename CapDescriptor::Builder descriptor, kj::Own<ClientHook>&& cap) = 0;
// Fill in the given descriptor so that it describes the given capability.
virtual kj::Own<ClientHook> getInjectedCap(typename CapDescriptor::Reader descriptor) = 0;
// Read back a cap that was previously injected with `injectCap`. This should return a new
// reference.
private:
void injectCapInternal(_::PointerBuilder builder, kj::Own<ClientHook>&& cap) override final {
injectCap(
typename CapDescriptor::Builder(builder.initCapDescriptor(_::structSize<CapDescriptor>())),
kj::mv(cap));
}
kj::Own<ClientHook> getInjectedCapInternal(const _::StructReader& capDescriptor) {
return getInjectedCap(typename CapDescriptor::Reader(capDescriptor));
}
};
// -------------------------------------------------------------------
class CapReaderContext {
// Class which can "imbue" reader objects from some other message with a capability context,
// so that interface pointers found in the message can be extracted and called.
//
// `imbue()` can only be called once per context.
public:
CapReaderContext(CapExtractorBase& extractor);
~CapReaderContext() noexcept(false);
ObjectPointer::Reader imbue(ObjectPointer::Reader base);
private:
CapExtractorBase* extractor; // becomes null once arena is allocated
void* arenaSpace[12 + sizeof(kj::MutexGuarded<void*>) / sizeof(void*)];
_::ImbuedReaderArena& arena() { return *reinterpret_cast<_::ImbuedReaderArena*>(arenaSpace); }
friend class _::ImbuedReaderArena;
};
class CapBuilderContext {
// Class which can "imbue" reader objects from some other message with a capability context,
// so that interface pointers found in the message can be set to point at live capabilities.
//
// `imbue()` can only be called once per context.
public:
CapBuilderContext(CapInjectorBase& injector);
~CapBuilderContext() noexcept(false);
ObjectPointer::Builder imbue(ObjectPointer::Builder base);
private:
CapInjectorBase* injector; // becomes null once arena is allocated
void* arenaSpace[12 + sizeof(kj::MutexGuarded<void*>) / sizeof(void*)];
_::ImbuedBuilderArena& arena() { return *reinterpret_cast<_::ImbuedBuilderArena*>(arenaSpace); }
friend class _::ImbuedBuilderArena;
};
} // namespace capnp
#endif // CAPNP_CAPABILITY_CONTEXT_H_
......@@ -24,19 +24,18 @@
#include "capability.h"
namespace capnp {
namespace _ { // private
CallResult::Pipeline CallResult::Pipeline::getPointerField(uint16_t pointerIndex) const {
auto newOps = kj::heapArray<PipelineManager::Op>(ops.size() + 1);
TypelessAnswer::Pipeline TypelessAnswer::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 = PipelineManager::Op::GET_POINTER_FIELD;
newOp.type = PipelineOp::GET_POINTER_FIELD;
newOp.pointerIndex = pointerIndex;
return Pipeline(call->addRef(), kj::mv(newOps));
return Pipeline(hook->addRef(), kj::mv(newOps));
}
} // namespace _ (private)
} // namespace capnp
......@@ -29,221 +29,356 @@
namespace capnp {
template <typename Answer>
class Response;
template <typename T>
class RemotePromise: public kj::Promise<kj::Own<ReaderFor<T>>>, public T::Pipeline {
// A Promise which supports pipelined calls. T is typically a struct type.
class RemotePromise: public kj::Promise<Response<T>>, public T::Pipeline {
// A Promise which supports pipelined calls. T is typically a struct type. T must declare
// an inner "mix-in" type "Pipeline" which implements pipelining; RemotePromise simply
// multiply-inherits that type along with Promise<Response<T>>. T::Pipeline must be movable,
// but does not need to be copyable (i.e. just like Promise<T>).
//
// The promise is for an owned pointer so that the RPC system can allocate the MessageReader
// itself.
public:
inline RemotePromise(kj::Promise<kj::Own<ReaderFor<T>>>&& promise,
typename T::Pipeline&& pipeline)
: kj::Promise<kj::Own<ReaderFor<T>>>(kj::mv(promise)),
inline RemotePromise(kj::Promise<Response<T>>&& promise, typename T::Pipeline&& pipeline)
: kj::Promise<Response<T>>(kj::mv(promise)),
T::Pipeline(kj::mv(pipeline)) {}
inline RemotePromise(decltype(nullptr))
: kj::Promise<kj::Own<ReaderFor<T>>>(nullptr) {}
: kj::Promise<Response<T>>(nullptr) {}
KJ_DISALLOW_COPY(RemotePromise);
RemotePromise(RemotePromise&& other) = default;
RemotePromise& operator=(RemotePromise&& other) = default;
};
struct Capability {
// A capability without type-safe methods. Typed capability clients wrap `Client` and typed
// capability servers subclass `Server` to dispatch to the regular, typed methods.
class Client;
class Server;
};
// =======================================================================================
class Call;
struct CallResult;
class RequestHook;
class ResponseHook;
class PipelineHook;
class ClientHook;
class TypelessCapability {
// This is an internal type used to represent a live capability (or a promise for a capability).
// This class should not be used directly by applications; it is intended to be used by the
// generated code wrappers.
template <typename Params, typename Answer>
class Request: public Params::Builder {
// A call that hasn't been sent yet. This class extends a Builder for the call's "Params"
// structure with a method send() that actually sends it.
//
// Given a Cap'n Proto method `foo(a :A, b :B): C`, the generated client interface will have
// a method `Request<FooParams, C> startFoo()` (as well as a convenience method
// `RemotePromise<C> foo(A::Reader a, B::Reader b)`).
public:
virtual kj::Own<Call> newCall(uint64_t interfaceId, uint16_t methodId) const = 0;
// Begin a new call to a method of this capability.
inline Request(typename Params::Builder builder, kj::Own<RequestHook>&& hook)
: Params::Builder(builder), hook(kj::mv(hook)) {}
virtual kj::Own<TypelessCapability> addRef() const = 0;
// Return a new reference-counted pointer to the same capability. (Reference counting can be
// implemented using a special Disposer, so that the returned pointer actually has the same
// identity as the original.)
RemotePromise<Answer> send();
// Send the call and return a promise for the answer.
virtual kj::Promise<void> whenResolved() const = 0;
private:
kj::Own<RequestHook> hook;
};
template <typename Answer>
class Response: public Answer::Reader {
// A completed call. This class extends a Reader for the call's answer structure. The Response
// is move-only -- once it goes out-of-scope, the underlying message will be freed.
public:
inline Response(typename Answer::Reader reader, kj::Own<ResponseHook>&& hook)
: Answer::Reader(reader), hook(kj::mv(hook)) {}
private:
kj::Own<ResponseHook> hook;
template <typename, typename>
friend class Request;
};
class Capability::Client {
// Base type for capability clients.
public:
explicit Client(kj::Own<ClientHook>&& hook);
Client(const Client& other);
Client& operator=(const Client& other);
// Copies by reference counting. Warning: Refcounting is slow due to atomic ops. Try to only
// use move instead.
Client(Client&&) = default;
Client& operator=(Client&&) = default;
// Move is fast.
kj::Promise<void> whenResolved() const;
// If the capability is actually only a promise, the returned promise resolves once the
// capability itself has resolved to its final destination (or propagates the exception if
// the capability promise is rejected). This is mainly useful for error-checking in the case
// where no calls are being made. There is no reason to wait for this before making calls; if
// the capability does not resolve, the call results will propagate the error.
// TODO(soon): method implementing Join
// TODO(soon): method(s) for Join
private:
kj::Own<ClientHook> hook;
};
// =======================================================================================
// Local capabilities
class CallContextHook;
template <typename T>
class CallContext: public kj::DisallowConstCopy {
// Wrapper around TypelessCallContext with a specific return type.
class Call {
public:
virtual ObjectPointer::Builder getRequest() = 0;
// Get the request object for this call, to be filled in before sending.
explicit CallContext(kj::Own<CallContextHook> hook);
typename T::Builder getAnswer();
typename T::Builder initAnswer();
typename T::Builder initAnswer(uint size);
void setAnswer(typename T::Reader value);
void adoptAnswer(Orphan<T>&& value);
Orphanage getAnswerOrphanage();
// Manipulate the answer (return value) payload. The "Return" message (part of the RPC protocol)
// will typically be allocated the first time one of these is called. Some RPC systems may
// allocate these messages in a limited space (such as a shared memory segment), therefore the
// application should delay calling these as long as is convenient to do so (but don't delay
// if doing so would require extra copies later).
void allowAsyncCancellation(bool allow = true);
// Indicate that it is OK for the RPC system to discard its Promise for this call's result if
// the caller cancels the call, thereby transitively canceling any asynchronous operations the
// call implementation was performing. This is not done by default because it could represent a
// security risk: applications must be carefully written to ensure that they do not end up in
// a bad state if an operation is canceled at an arbitrary point. However, for long-running
// method calls that hold significant resources, prompt cancellation is often useful.
//
// You can also switch back to disallowing cancellation by passing `false` as the argument.
//
// Keep in mind that asynchronous cancellation cannot occur while the method is synchronously
// executing on a local thread. The method must perform an asynchronous operation or call
// `EventLoop::current().runLater()` to yield control.
bool isCanceled();
// As an alternative to `allowAsyncCancellation()`, a server can call this to check for
// cancellation.
//
// Keep in mind that if the method is blocking the event loop, the cancel message won't be
// received, so it is necessary to use `EventLoop::current().runLater()` occasionally.
virtual RemotePromise<CallResult> send() = 0;
// Send the call and return a promise for the result.
private:
kj::Own<CallContextHook> hook;
};
class CallRunner {
// Implements pipelined requests for a particular outstanding call.
class Capability::Server {
// Objects implementing a Cap'n Proto interface must subclass this. Typically, such objects
// will instead subclass a typed Server interface which will take care of implementing
// dispatchCall().
public:
virtual kj::Own<CallRunner> addRef() const = 0;
// Increment this object's reference count.
virtual kj::Promise<void> dispatchCall(uint64_t interfaceId, uint16_t methodId,
kj::Own<ObjectPointer::Reader> params,
CallContext<ObjectPointer> context) = 0;
// Call the given method. `params` is the input struct, and should be released as soon as it
// is no longer needed. `context` may be used to allocate the output struct and deal with
// cancellation.
// TODO(soon): Method which can optionally be overridden to implement Join when the object is
// a proxy.
};
Capability::Client makeLocalClient(kj::Own<Capability::Server>&& server);
// Make a client capability that wraps the given server capability.
// =======================================================================================
struct PipelineOp {
enum Type {
GET_POINTER_FIELD
};
struct PipelineOp {
enum Type {
GET_POINTER_FIELD
Type type;
union {
uint16_t pointerIndex;
};
// There may be other types in the future...
};
virtual kj::Own<TypelessCapability> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) const = 0;
// Extract a promised Capability from the answer.
Type type;
union {
uint16_t pointerIndex; // for GET_POINTER_FIELD
};
};
struct CallResult {
// Result of a call. Designed to be used as RemotePromise<CallResult>.
struct TypelessAnswer {
// 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<CallRunner>&& runner): runner(kj::mv(runner)) {}
inline explicit Pipeline(kj::Own<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.
// Make `ops` into a Vector so that it can be extended without allocation in this case.
// Also make `ops` into a Vector to optimize this.
inline kj::Own<TypelessCapability> asCap() const { return runner->getPipelinedCap(ops); }
Capability::Client asCap() const;
// Expect that the result is a capability and construct a pipelined version of it now.
private:
kj::Own<CallRunner> runner;
kj::Array<CallRunner::PipelineOp> ops;
kj::Own<PipelineHook> hook;
kj::Array<PipelineOp> ops;
inline Pipeline(kj::Own<CallRunner>&& runner, kj::Array<CallRunner::PipelineOp>&& ops)
: runner(kj::mv(runner)), ops(kj::mv(ops)) {}
inline Pipeline(kj::Own<PipelineHook>&& hook, kj::Array<PipelineOp>&& ops)
: hook(kj::mv(hook)), ops(kj::mv(ops)) {}
};
};
// =======================================================================================
// Classes for imbuing message readers/builders with a capability context.
//
// These classes are for use by RPC implementations. Application code need not know about them.
//
// TODO(kenton): Move these to a separate header.
//
// Normally, MessageReader and MessageBuilder do not support interface pointers because they
// are not RPC-aware and so have no idea how to convert between a serialized CapabilityDescriptor
// and a live capability. To fix this, a reader/builder object needs to be "imbued" with a
// capability context. This creates a new reader/builder which points at the same object but has
// the ability to deal with interface fields.
namespace _ { // private
class ImbuedReaderArena;
class ImbuedBuilderArena;
} // namespace _ (private)
class CapExtractorBase {
// Non-template base class for CapExtractor<T>.
// 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.
private:
virtual kj::Own<TypelessCapability> extractCapInternal(const _::StructReader& capDescriptor) = 0;
friend class _::ImbuedReaderArena;
};
class RequestHook {
// Hook interface implemented by RPC system representing a request being built.
class CapInjectorBase {
// Non-template base class for CapInjector<T>.
public:
virtual ObjectPointer::Builder getRequest() = 0;
// Get the request object for this call, to be filled in before sending.
private:
virtual void injectCapInternal(_::PointerBuilder builder, kj::Own<TypelessCapability>&& cap) = 0;
friend class _::ImbuedBuilderArena;
virtual RemotePromise<TypelessAnswer> send() = 0;
// Send the call and return a promise for the result.
};
template <typename CapDescriptor>
class CapExtractor: public CapExtractorBase {
// Callback used to read a capability from a message, implemented by the RPC system.
// `CapDescriptor` is the struct type which the RPC implementation uses to represent
// capabilities. (On the wire, an interface pointer actually points to a struct of this type.)
class ResponseHook {
// Hook interface implemented by RPC system representing a response.
//
// At present this class has no methods. It exists only for garbage collection -- when the
// ResponseHook is destroyed, the answer can be freed.
public:
virtual kj::Own<TypelessCapability> extractCap(typename CapDescriptor::Reader descriptor) = 0;
// Given the descriptor read off the wire, construct a live capability.
private:
kj::Own<TypelessCapability> extractCapInternal(
const _::StructReader& capDescriptor) override final {
return extractCap(typename CapDescriptor::Reader(capDescriptor));
}
};
template <typename CapDescriptor>
class CapInjector: public CapInjectorBase {
// Callback used to write a capability into a message, implemented by the RPC system.
// `CapDescriptor` is the struct type which the RPC implementation uses to represent
// capabilities. (On the wire, an interface pointer actually points to a struct of this type.)
class PipelineHook {
// Represents a currently-running call, and implements pipelined requests on its result.
public:
virtual void injectCap(typename CapDescriptor::Builder descriptor,
kj::Own<TypelessCapability>&& cap) = 0;
// Fill in the given descriptor so that it describes the given capability.
virtual kj::Own<PipelineHook> addRef() const = 0;
// Increment this object's reference count.
private:
void injectCapInternal(_::PointerBuilder builder,
kj::Own<TypelessCapability>&& cap) override final {
injectCap(
typename CapDescriptor::Builder(builder.initCapDescriptor(_::structSize<CapDescriptor>())),
kj::mv(cap));
}
virtual kj::Own<ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) const = 0;
// Extract a promised Capability from the answer.
};
class CapReaderContext {
// Class which can "imbue" reader objects from some other message with a capability context,
// so that interface pointers found in the message can be extracted and called.
class ClientHook {
public:
CapReaderContext(Orphanage arena, CapExtractorBase& extractor);
~CapReaderContext() noexcept(false);
ObjectPointer::Reader imbue(ObjectPointer::Reader base);
private:
void* arenaSpace[15 + sizeof(kj::MutexGuarded<void*>) / sizeof(void*)];
virtual Request<ObjectPointer, TypelessAnswer> newCall(
uint64_t interfaceId, uint16_t methodId) const = 0;
virtual kj::Promise<void> whenResolved() const = 0;
virtual kj::Own<TypelessCapability> extractCapInternal(const _::StructReader& capDescriptor) = 0;
virtual kj::Own<ClientHook> addRef() const = 0;
// Return a new reference to the same capability.
friend class _::ImbuedReaderArena;
virtual void* getBrand() const = 0;
// Returns a void* that identifies who made this client. This can be used by an RPC adapter to
// discover when a capability it needs to marshal is one that it created in the first place, and
// therefore it can transfer the capability without proxying.
};
class CapBuilderContext {
// Class which can "imbue" reader objects from some other message with a capability context,
// so that interface pointers found in the message can be set to point at live capabilities.
class CallContextHook {
// Hook interface implemented by RPC system to manage a call on the server side. See
// CallContext<T>.
public:
CapBuilderContext(Orphanage arena, CapInjectorBase& injector);
~CapBuilderContext() noexcept(false);
ObjectPointer::Builder imbue(ObjectPointer::Builder base);
private:
void* arenaSpace[15 + sizeof(kj::MutexGuarded<void*>) / sizeof(void*)];
virtual ObjectPointer::Builder getAnswer() = 0;
virtual void allowAsyncCancellation(bool allow) = 0;
virtual bool isCanceled() = 0;
};
virtual void injectCapInternal(_::PointerBuilder builder, kj::Own<TypelessCapability>&& cap) = 0;
// =======================================================================================
// Inline implementation details
template <typename Params, typename Answer>
RemotePromise<Answer> Request<Params, Answer>::send() {
auto typelessPromise = hook->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<TypelessAnswer>>&>(typelessPromise)
.then([](Response<TypelessAnswer>&& response) -> Response<Answer> {
return Response<Answer>(response.getAs<Answer>(), kj::mv(response.hook));
});
// Wrap the typeless pipeline in a typed wrapper.
typename Answer::Pipeline typedPipeline(
kj::mv(kj::implicitCast<TypelessAnswer::Pipeline&>(typelessPromise)));
return RemotePromise<Answer>(kj::mv(typedPromise), kj::mv(typedPipeline));
}
inline Capability::Client TypelessAnswer::Pipeline::asCap() const {
return Capability::Client(hook->getPipelinedCap(ops));
}
inline Capability::Client::Client(kj::Own<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) {
hook = other.hook->addRef();
return *this;
}
inline kj::Promise<void> Capability::Client::whenResolved() const {
return hook->whenResolved();
}
friend class _::ImbuedBuilderArena;
};
template <typename T>
inline CallContext<T>::CallContext(kj::Own<CallContextHook> hook): hook(kj::mv(hook)) {}
template <typename T>
inline typename T::Builder CallContext<T>::getAnswer() {
// `template` keyword needed due to: http://llvm.org/bugs/show_bug.cgi?id=17401
return hook->getAnswer().template getAs<T>();
}
template <typename T>
inline typename T::Builder CallContext<T>::initAnswer() {
// `template` keyword needed due to: http://llvm.org/bugs/show_bug.cgi?id=17401
return hook->getAnswer().template initAs<T>();
}
template <typename T>
inline typename T::Builder CallContext<T>::initAnswer(uint size) {
// `template` keyword needed due to: http://llvm.org/bugs/show_bug.cgi?id=17401
return hook->getAnswer().template initAs<T>(size);
}
template <typename T>
inline void CallContext<T>::setAnswer(typename T::Reader value) {
hook->getAnswer().set(value);
}
template <typename T>
inline void CallContext<T>::adoptAnswer(Orphan<T>&& value) {
hook->getAnswer().adopt(kj::mv(value));
}
template <typename T>
inline Orphanage CallContext<T>::getAnswerOrphanage() {
return Orphanage::getForMessageContaining(hook->getAnswer());
}
template <typename T>
inline void CallContext<T>::allowAsyncCancellation(bool allow) {
hook->allowAsyncCancellation(allow);
}
template <typename T>
inline bool CallContext<T>::isCanceled() {
return hook->isCanceled();
}
} // namespace capnp
......
......@@ -2104,6 +2104,14 @@ PointerReader PointerBuilder::asReader() const {
return PointerReader(segment, pointer, std::numeric_limits<int>::max());
}
BuilderArena& PointerBuilder::getArena() const {
return *segment->getArena();
}
PointerBuilder PointerBuilder::imbue(ImbuedBuilderArena& newArena) const {
return PointerBuilder(newArena.imbue(segment), pointer);
}
// =======================================================================================
// PointerReader
......@@ -2139,6 +2147,14 @@ bool PointerReader::isNull() const {
return pointer == nullptr || pointer->isNull();
}
kj::Maybe<Arena&> PointerReader::getArena() const {
return segment == nullptr ? nullptr : segment->getArena();
}
PointerReader PointerReader::imbue(ImbuedReaderArena& newArena) const {
return PointerReader(newArena.imbue(segment), pointer, nestingLimit);
}
// =======================================================================================
// StructBuilder
......
......@@ -39,7 +39,7 @@
namespace capnp {
class TypelessCapability;
class ClientHook;
namespace _ { // private
......@@ -54,7 +54,10 @@ struct WirePointer;
struct WireHelpers;
class SegmentReader;
class SegmentBuilder;
class Arena;
class BuilderArena;
class ImbuedReaderArena;
class ImbuedBuilderArena;
// =============================================================================
......@@ -284,7 +287,7 @@ public:
ListBuilder getList(FieldSize elementSize, const word* defaultValzue);
ListBuilder getStructList(StructSize elementSize, const word* defaultValue);
template <typename T> typename T::Builder getBlob(const void* defaultValue,ByteCount defaultSize);
kj::Own<TypelessCapability> getCapability();
kj::Own<ClientHook> getCapability();
// Get methods: Get the value. If it is null, initialize it to a copy of the default value.
// The default value is encoded as an "unchecked message" for structs, lists, and objects, or a
// simple byte array for blobs.
......@@ -300,7 +303,7 @@ public:
void setStruct(const StructReader& value);
void setList(const ListReader& value);
template <typename T> void setBlob(typename T::Reader value);
void setCapability(kj::Own<TypelessCapability>&& cap);
void setCapability(kj::Own<ClientHook>&& cap);
// Set methods: Initialize the pointer to a newly-allocated copy of the given value, discarding
// the existing object.
......@@ -321,6 +324,12 @@ public:
PointerReader asReader() const;
BuilderArena& getArena() const;
// Get the arena containing this pointer.
PointerBuilder imbue(ImbuedBuilderArena& newArena) const;
// Imbue the pointer with a capability context, returning the imbued pointer.
private:
SegmentBuilder* segment; // Memory segment in which the pointer resides.
WirePointer* pointer; // Pointer to the pointer.
......@@ -342,7 +351,7 @@ public:
ListReader getList(FieldSize expectedElementSize, const word* defaultValue) const;
template <typename T>
typename T::Reader getBlob(const void* defaultValue, ByteCount defaultSize) const;
kj::Own<TypelessCapability> getCapability();
kj::Own<ClientHook> getCapability();
// Get methods: Get the value. If it is null, return the default value instead.
// The default value is encoded as an "unchecked message" for structs, lists, and objects, or a
// simple byte array for blobs.
......@@ -352,6 +361,12 @@ public:
// word* can actually be passed to readUnchecked() to read the designated sub-object later. If
// this isn't an unchecked message, throws an exception.
kj::Maybe<Arena&> getArena() const;
// Get the arena containing this pointer.
PointerReader imbue(ImbuedReaderArena& newArena) const;
// Imbue the pointer with a capability context, returning the imbued pointer.
private:
SegmentReader* segment; // Memory segment in which the pointer resides.
const WirePointer* pointer; // Pointer to the pointer. null = treat as null pointer.
......
......@@ -63,6 +63,7 @@ struct ObjectPointer {
_::PointerReader reader;
friend struct ObjectPointer;
friend class Orphanage;
friend class CapReaderContext;
};
class Builder {
......@@ -143,6 +144,7 @@ struct ObjectPointer {
private:
_::PointerBuilder builder;
friend class CapBuilderContext;
};
};
......@@ -281,6 +283,38 @@ inline Orphan<T> Orphan<ObjectPointer>::releaseAs() {
return Orphan<T>(kj::mv(builder));
}
// Using ObjectPointer as the template type should work...
template <>
inline typename ObjectPointer::Reader ObjectPointer::Reader::getAs<ObjectPointer>() {
return *this;
}
template <>
inline typename ObjectPointer::Builder ObjectPointer::Builder::getAs<ObjectPointer>() {
return *this;
}
template <>
inline typename ObjectPointer::Builder ObjectPointer::Builder::initAs<ObjectPointer>() {
clear();
return *this;
}
template <>
inline void ObjectPointer::Builder::setAs<ObjectPointer>(ObjectPointer::Reader value) {
return builder.copyFrom(value.reader);
}
template <>
inline void ObjectPointer::Builder::adopt<ObjectPointer>(Orphan<ObjectPointer>&& orphan) {
builder.adopt(kj::mv(orphan.builder));
}
template <>
inline Orphan<ObjectPointer> ObjectPointer::Builder::disownAs<ObjectPointer>() {
return Orphan<ObjectPointer>(builder.disown());
}
template <>
inline Orphan<ObjectPointer> Orphan<ObjectPointer>::releaseAs() {
return kj::mv(*this);
}
} // namespace capnp
#endif // CAPNP_OBJECT_H_
......@@ -950,7 +950,7 @@ using JoinAnswer = Object;
#
# # Level 4 features -----------------------------------------------
#
# newJoiner(count :UInt32): NewJoinerResponse;
# newJoiner(count :UInt32) :NewJoinerResponse;
# # Prepare a new Join operation, which will eventually lead to forming a new direct connection
# # to the host of the joined capability. `count` is the number of capabilities to join.
#
......@@ -973,7 +973,7 @@ using JoinAnswer = Object;
# # message on it with the specified `ProvisionId` in order to receive the final capability.
# }
#
# acceptConnectionFromJoiner(parts: List(JoinKeyPart), paths :List(VatPath))
# acceptConnectionFromJoiner(parts :List(JoinKeyPart), paths :List(VatPath))
# :ConnectionAndProvisionId;
# # Called on a joined capability's host to receive the connection from the joiner, once all
# # key parts have arrived. The caller should expect to receive an `Accept` message over the
......@@ -1002,17 +1002,17 @@ using JoinAnswer = Object;
# sendToTarget :RecipientId;
# }
#
# connectToIntroduced(capId: ThirdPartyCapId) :ConnectionAndProvisionId;
# connectToIntroduced(capId :ThirdPartyCapId) :ConnectionAndProvisionId;
# # Given a ThirdPartyCapId received over this connection, connect to the third party. The
# # caller should then send an `Accept` message over the new connection.
#
# acceptIntroducedConnection(recipientId: RecipientId): Connection
# acceptIntroducedConnection(recipientId :RecipientId) :Connection;
# # Given a RecipientId received in a `Provide` message on this `Connection`, wait for the
# # recipient to connect, and return the connection formed. Usually, the first message received
# # on the new connection will be an `Accept` message.
# }
#
# sturct ConnectionAndProvisionId {
# struct ConnectionAndProvisionId {
# # **(level 3)**
#
# connection :Connection;
......
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