Commit f740a60f authored by Kenton Varda's avatar Kenton Varda

Cleaner disconnect handling. Better fix for issue #71, and also simplifies the…

Cleaner disconnect handling.  Better fix for issue #71, and also simplifies the interface and improves robustness.
parent 1fddf5a6
...@@ -578,9 +578,7 @@ public: ...@@ -578,9 +578,7 @@ public:
Request<AnyPointer, AnyPointer> newCall( Request<AnyPointer, AnyPointer> newCall(
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override { uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override {
auto hook = kj::heap<BrokenRequest>(exception, sizeHint); return newBrokenRequest(kj::cp(exception), sizeHint);
auto root = hook->message.getRoot<AnyPointer>();
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
} }
VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId, VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
...@@ -626,4 +624,11 @@ kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason) { ...@@ -626,4 +624,11 @@ kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason) {
return kj::refcounted<BrokenPipeline>(kj::mv(reason)); return kj::refcounted<BrokenPipeline>(kj::mv(reason));
} }
Request<AnyPointer, AnyPointer> newBrokenRequest(
kj::Exception&& reason, kj::Maybe<MessageSize> sizeHint) {
auto hook = kj::heap<BrokenRequest>(kj::mv(reason), sizeHint);
auto root = hook->message.getRoot<AnyPointer>();
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
}
} // namespace capnp } // namespace capnp
...@@ -419,6 +419,10 @@ kj::Own<ClientHook> newBrokenCap(kj::Exception&& reason); ...@@ -419,6 +419,10 @@ kj::Own<ClientHook> newBrokenCap(kj::Exception&& reason);
kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason); kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason);
// Helper function that creates a pipeline which simply throws exceptions when called. // Helper function that creates a pipeline which simply throws exceptions when called.
Request<AnyPointer, AnyPointer> newBrokenRequest(
kj::Exception&& reason, kj::Maybe<MessageSize> sizeHint);
// Helper function that creates a Request object that simply throws exceptions when sent.
// ======================================================================================= // =======================================================================================
// Extend PointerHelpers for interfaces // Extend PointerHelpers for interfaces
......
...@@ -240,7 +240,7 @@ struct EzRpcServer::Impl final: public SturdyRefRestorer<Text>, public kj::TaskS ...@@ -240,7 +240,7 @@ struct EzRpcServer::Impl final: public SturdyRefRestorer<Text>, public kj::TaskS
// Arrange to destroy the server context when all references are gone, or when the // Arrange to destroy the server context when all references are gone, or when the
// EzRpcServer is destroyed (which will destroy the TaskSet). // EzRpcServer is destroyed (which will destroy the TaskSet).
tasks.add(server->network.onDrained().attach(kj::mv(server))); tasks.add(server->network.onDisconnect().attach(kj::mv(server)));
}))); })));
} }
......
...@@ -66,7 +66,6 @@ kj::AsyncIoProvider::PipeThread runServer(kj::AsyncIoProvider& ioProvider, int& ...@@ -66,7 +66,6 @@ kj::AsyncIoProvider::PipeThread runServer(kj::AsyncIoProvider& ioProvider, int&
TestRestorer restorer(callCount); TestRestorer restorer(callCount);
auto server = makeRpcServer(network, restorer); auto server = makeRpcServer(network, restorer);
network.onDisconnect().wait(waitScope); network.onDisconnect().wait(waitScope);
network.onDrained().wait(waitScope);
}); });
} }
...@@ -141,9 +140,7 @@ TEST(TwoPartyNetwork, Pipelining) { ...@@ -141,9 +140,7 @@ TEST(TwoPartyNetwork, Pipelining) {
auto rpcClient = makeRpcClient(network); auto rpcClient = makeRpcClient(network);
bool disconnected = false; bool disconnected = false;
bool drained = false;
kj::Promise<void> disconnectPromise = network.onDisconnect().then([&]() { disconnected = true; }); kj::Promise<void> disconnectPromise = network.onDisconnect().then([&]() { disconnected = true; });
kj::Promise<void> drainedPromise = network.onDrained().then([&]() { drained = true; });
{ {
// Request the particular capability from the server. // Request the particular capability from the server.
...@@ -182,14 +179,12 @@ TEST(TwoPartyNetwork, Pipelining) { ...@@ -182,14 +179,12 @@ TEST(TwoPartyNetwork, Pipelining) {
} }
EXPECT_FALSE(disconnected); EXPECT_FALSE(disconnected);
EXPECT_FALSE(drained);
// What if we disconnect? // What if we disconnect?
serverThread.pipe->shutdownWrite(); serverThread.pipe->shutdownWrite();
// The other side should also disconnect. // The other side should also disconnect.
disconnectPromise.wait(ioContext.waitScope); disconnectPromise.wait(ioContext.waitScope);
EXPECT_FALSE(drained);
{ {
// Use the now-broken capability. // Use the now-broken capability.
...@@ -213,11 +208,7 @@ TEST(TwoPartyNetwork, Pipelining) { ...@@ -213,11 +208,7 @@ TEST(TwoPartyNetwork, Pipelining) {
EXPECT_EQ(3, callCount); EXPECT_EQ(3, callCount);
EXPECT_EQ(1, reverseCallCount); EXPECT_EQ(1, reverseCallCount);
} }
EXPECT_FALSE(drained);
} }
drainedPromise.wait(ioContext.waitScope);
} }
} // namespace } // namespace
......
...@@ -30,37 +30,20 @@ namespace capnp { ...@@ -30,37 +30,20 @@ namespace capnp {
TwoPartyVatNetwork::TwoPartyVatNetwork(kj::AsyncIoStream& stream, rpc::twoparty::Side side, TwoPartyVatNetwork::TwoPartyVatNetwork(kj::AsyncIoStream& stream, rpc::twoparty::Side side,
ReaderOptions receiveOptions) ReaderOptions receiveOptions)
: stream(stream), side(side), receiveOptions(receiveOptions), previousWrite(kj::READY_NOW) { : stream(stream), side(side), receiveOptions(receiveOptions), previousWrite(kj::READY_NOW) {
{
auto paf = kj::newPromiseAndFulfiller<void>(); auto paf = kj::newPromiseAndFulfiller<void>();
drainedPromise = paf.promise.fork(); disconnectPromise = paf.promise.fork();
drainedFulfiller.fulfiller = kj::mv(paf.fulfiller); disconnectFulfiller.fulfiller = kj::mv(paf.fulfiller);
}
{
auto paf = kj::newPromiseAndFulfiller<void>();
// If the RPC system on this side drops the connection, thus firing onDrained() before
// onDisconnected(), we also want to consider ourselves disconnected. Otherwise, we might
// not detect actual disconnect because the RPC system won't attempt to send or receive any
// more messages on the connection. So, we exclusive-join the disconnect promise with the
// first branch of drainedPromise.
disconnectPromise = paf.promise.exclusiveJoin(drainedPromise.addBranch()).fork();
disconnectFulfiller = kj::mv(paf.fulfiller);
}
} }
void TwoPartyVatNetwork::FulfillerDisposer::disposeImpl(void* pointer) const { void TwoPartyVatNetwork::FulfillerDisposer::disposeImpl(void* pointer) const {
KJ_DBG("deref", this, refcount);
if (--refcount == 0) { if (--refcount == 0) {
fulfiller->fulfill(); fulfiller->fulfill();
} }
} }
kj::Own<TwoPartyVatNetworkBase::Connection> TwoPartyVatNetwork::asConnection() { kj::Own<TwoPartyVatNetworkBase::Connection> TwoPartyVatNetwork::asConnection() {
KJ_DBG("ref", &drainedFulfiller, drainedFulfiller.refcount); ++disconnectFulfiller.refcount;
++drainedFulfiller.refcount; return kj::Own<TwoPartyVatNetworkBase::Connection>(this, disconnectFulfiller);
return kj::Own<TwoPartyVatNetworkBase::Connection>(this, drainedFulfiller);
} }
kj::Maybe<kj::Own<TwoPartyVatNetworkBase::Connection>> TwoPartyVatNetwork::connectToRefHost( kj::Maybe<kj::Own<TwoPartyVatNetworkBase::Connection>> TwoPartyVatNetwork::connectToRefHost(
...@@ -102,12 +85,10 @@ public: ...@@ -102,12 +85,10 @@ public:
void send() override { void send() override {
network.previousWrite = network.previousWrite.then([&]() { network.previousWrite = network.previousWrite.then([&]() {
auto promise = writeMessage(network.stream, message).then([]() { // Note that if the write fails, all further writes will be skipped due to the exception.
// success; do nothing // We never actually handle this exception because we assume the read end will fail as well
}, [&](kj::Exception&& exception) { // and it's cleaner to handle the failure there.
// Exception during write! auto promise = writeMessage(network.stream, message).eagerlyEvaluate(nullptr);
network.disconnectFulfiller->fulfill();
}).eagerlyEvaluate(nullptr);
return kj::mv(promise); return kj::mv(promise);
}).attach(kj::addRef(*this)); }).attach(kj::addRef(*this));
} }
...@@ -145,13 +126,8 @@ kj::Promise<kj::Maybe<kj::Own<IncomingRpcMessage>>> TwoPartyVatNetwork::receiveI ...@@ -145,13 +126,8 @@ kj::Promise<kj::Maybe<kj::Own<IncomingRpcMessage>>> TwoPartyVatNetwork::receiveI
KJ_IF_MAYBE(m, message) { KJ_IF_MAYBE(m, message) {
return kj::Own<IncomingRpcMessage>(kj::heap<IncomingMessageImpl>(kj::mv(*m))); return kj::Own<IncomingRpcMessage>(kj::heap<IncomingMessageImpl>(kj::mv(*m)));
} else { } else {
disconnectFulfiller->fulfill();
return nullptr; return nullptr;
} }
}, [&](kj::Exception&& exception) {
disconnectFulfiller->fulfill();
kj::throwRecoverableException(kj::mv(exception));
return nullptr;
}); });
}); });
} }
......
...@@ -49,19 +49,6 @@ public: ...@@ -49,19 +49,6 @@ public:
kj::Promise<void> onDisconnect() { return disconnectPromise.addBranch(); } kj::Promise<void> onDisconnect() { return disconnectPromise.addBranch(); }
// Returns a promise that resolves when the peer disconnects. // Returns a promise that resolves when the peer disconnects.
//
// TODO(soon): Currently this fires when the underlying physical connection breaks. It should
// fire after the RPC system has detected EOF itself and dropped its connection reference, so
// that it has a chance to reply to connections ended cleanly.
kj::Promise<void> onDrained() { return drainedPromise.addBranch(); }
// Returns a promise that resolves once the peer has disconnected *and* all local objects
// referencing this connection have been destroyed. A caller might use this to decide when it
// is safe to destroy the RpcSystem, if it isn't able to reliably destroy all objects using it
// directly.
//
// TODO(soon): This is not quite designed right. Those local objects should simply be disabled.
// Their existence should not prevent the RpcSystem from being destroyed.
// implements VatNetwork ----------------------------------------------------- // implements VatNetwork -----------------------------------------------------
...@@ -86,14 +73,12 @@ private: ...@@ -86,14 +73,12 @@ private:
// second call on the server side. Never fulfilled, because there is only one connection. // second call on the server side. Never fulfilled, because there is only one connection.
kj::ForkedPromise<void> disconnectPromise = nullptr; kj::ForkedPromise<void> disconnectPromise = nullptr;
kj::Own<kj::PromiseFulfiller<void>> disconnectFulfiller;
kj::ForkedPromise<void> drainedPromise = nullptr;
class FulfillerDisposer: public kj::Disposer { class FulfillerDisposer: public kj::Disposer {
// Hack: TwoPartyVatNetwork is both a VatNetwork and a VatNetwork::Connection. When all // Hack: TwoPartyVatNetwork is both a VatNetwork and a VatNetwork::Connection. Whet the RPC
// references to the Connection have been dropped, then we want onDrained() to fire. So we // system detects (or initiates) a disconnection, it drops its reference to the Connection.
// hand out Own<Connection>s with this disposer attached, so that we can detect when they are // When all references have been dropped, then we want onDrained() to fire. So we hand out
// dropped. // Own<Connection>s with this disposer attached, so that we can detect when they are dropped.
public: public:
mutable kj::Own<kj::PromiseFulfiller<void>> fulfiller; mutable kj::Own<kj::PromiseFulfiller<void>> fulfiller;
...@@ -101,7 +86,7 @@ private: ...@@ -101,7 +86,7 @@ private:
void disposeImpl(void* pointer) const override; void disposeImpl(void* pointer) const override;
}; };
FulfillerDisposer drainedFulfiller; FulfillerDisposer disconnectFulfiller;
kj::Own<TwoPartyVatNetworkBase::Connection> asConnection(); kj::Own<TwoPartyVatNetworkBase::Connection> asConnection();
// Returns a pointer to this with the disposer set to drainedFulfiller. // Returns a pointer to this with the disposer set to drainedFulfiller.
......
This diff is collapsed.
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