Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
5f6baed9
Commit
5f6baed9
authored
Oct 31, 2013
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First tests of RPC protocol implementation.
parent
9575df0c
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
819 additions
and
85 deletions
+819
-85
arena.c++
c++/src/capnp/arena.c++
+23
-13
arena.h
c++/src/capnp/arena.h
+2
-0
capability.c++
c++/src/capnp/capability.c++
+9
-1
object.h
c++/src/capnp/object.h
+13
-1
rpc-test.c++
c++/src/capnp/rpc-test.c++
+425
-0
rpc.c++
c++/src/capnp/rpc.c++
+218
-51
rpc.h
c++/src/capnp/rpc.h
+28
-7
test.capnp
c++/src/capnp/test.capnp
+14
-0
async-test.c++
c++/src/kj/async-test.c++
+9
-0
async.c++
c++/src/kj/async.c++
+17
-0
async.h
c++/src/kj/async.h
+61
-12
No files found.
c++/src/capnp/arena.c++
View file @
5f6baed9
...
...
@@ -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
());
...
...
c++/src/capnp/arena.h
View file @
5f6baed9
...
...
@@ -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.
...
...
c++/src/capnp/capability.c++
View file @
5f6baed9
...
...
@@ -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.
...
...
c++/src/capnp/object.h
View file @
5f6baed9
...
...
@@ -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
();
...
...
c++/src/capnp/rpc-test.c++
0 → 100644
View file @
5f6baed9
// 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
c++/src/capnp/rpc.c++
View file @
5f6baed9
...
...
@@ -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
.
then
InAnyThread
(
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
.
then
InAnyThread
(
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
)
{
...
...
c++/src/capnp/rpc.h
View file @
5f6baed9
...
...
@@ -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_
c++/src/capnp/test.capnp
View file @
5f6baed9
...
...
@@ -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 {}
c++/src/kj/async-test.c++
View file @
5f6baed9
...
...
@@ -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
());
...
...
c++/src/kj/async.c++
View file @
5f6baed9
...
...
@@ -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
));
}
...
...
c++/src/kj/async.h
View file @
5f6baed9
...
...
@@ -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
));
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment