Skip to content

Commit b47ea9f

Browse files
committed
proxy-io.h: Add DisconnectError exception
Throw disconnected exception from clientInvoke so IPC clients can easily detect when they are calling a remote method after the connection is closed. Before this, different exceptions were thrown which made this condition difficult to detect and handle.
1 parent dd1bf3d commit b47ea9f

File tree

3 files changed

+29
-13
lines changed

3 files changed

+29
-13
lines changed

include/mp/proxy-io.h

+7
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,13 @@ class Connection
365365
CleanupList m_async_cleanup_fns;
366366
};
367367

368+
//! Exception thrown when IPC client method is called after its connection is destroyed.
369+
class DisconnectError : public std::runtime_error
370+
{
371+
public:
372+
using runtime_error::runtime_error;
373+
};
374+
368375
//! Vat id for server side of connection. Required argument to RpcSystem::bootStrap()
369376
//!
370377
//! "Vat" is Cap'n Proto nomenclature for a host of various objects that facilitates

include/mp/proxy-types.h

+17-6
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,6 @@ void serverDestroy(Server& server)
582582
template <typename ProxyClient, typename GetRequest, typename... FieldObjs>
583583
void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields)
584584
{
585-
if (!proxy_client.m_context.connection) {
586-
throw std::logic_error("clientInvoke call made after disconnect");
587-
}
588585
if (!g_thread_context.waiter) {
589586
assert(g_thread_context.thread_name.empty());
590587
g_thread_context.thread_name = ThreadName(proxy_client.m_context.loop->m_exe_name);
@@ -606,7 +603,16 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
606603
std::exception_ptr exception;
607604
std::string kj_exception;
608605
bool done = false;
606+
const char* disconnected = nullptr;
609607
proxy_client.m_context.loop->sync([&]() {
608+
if (!proxy_client.m_context.connection) {
609+
const std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
610+
done = true;
611+
disconnected = "IPC client method called after disconnect.";
612+
invoke_context.thread_context.waiter->m_cv.notify_all();
613+
return;
614+
}
615+
610616
auto request = (proxy_client.m_client.*get_request)(nullptr);
611617
using Request = CapRequestTraits<decltype(request)>;
612618
using FieldList = typename ProxyClientMethodTraits<typename Request::Params>::Fields;
@@ -631,9 +637,13 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
631637
invoke_context.thread_context.waiter->m_cv.notify_all();
632638
},
633639
[&](const ::kj::Exception& e) {
634-
kj_exception = kj::str("kj::Exception: ", e).cStr();
635-
proxy_client.m_context.loop->logPlain()
636-
<< "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception;
640+
if (e.getType() == ::kj::Exception::Type::DISCONNECTED) {
641+
disconnected = "IPC client method call interrupted by disconnect.";
642+
} else {
643+
kj_exception = kj::str("kj::Exception: ", e).cStr();
644+
proxy_client.m_context.loop->logPlain()
645+
<< "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception;
646+
}
637647
const std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
638648
done = true;
639649
invoke_context.thread_context.waiter->m_cv.notify_all();
@@ -642,6 +652,7 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
642652

643653
std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
644654
invoke_context.thread_context.waiter->wait(lock, [&done]() { return done; });
655+
if (disconnected) throw DisconnectError(disconnected);
645656
if (exception) std::rethrow_exception(exception);
646657
if (!kj_exception.empty()) proxy_client.m_context.loop->raise() << kj_exception;
647658
}

test/mp/test/test.cpp

+5-7
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace test {
3535
* object destroys the client Connection object. Normally it makes sense for
3636
* this to be true to simplify shutdown and avoid needing to call
3737
* client_disconnect manually, but false allows testing more ProxyClient
38-
* behavior and the "clientInvoke call made after disconnect" code path.
38+
* behavior and the "IPC client method called after disconnect" code path.
3939
*/
4040
class TestSetup
4141
{
@@ -189,8 +189,8 @@ KJ_TEST("Call IPC method after client connection is closed")
189189
bool disconnected{false};
190190
try {
191191
foo->add(1, 2);
192-
} catch (const std::logic_error& e) {
193-
KJ_EXPECT(std::string_view{e.what()} == "clientInvoke call made after disconnect");
192+
} catch (const DisconnectError& e) {
193+
KJ_EXPECT(std::string_view{e.what()} == "IPC client method called after disconnect.");
194194
disconnected = true;
195195
}
196196
KJ_EXPECT(disconnected);
@@ -206,10 +206,8 @@ KJ_TEST("Calling IPC method after server connection is closed")
206206
bool disconnected{false};
207207
try {
208208
foo->add(1, 2);
209-
} catch (const std::runtime_error& e) {
210-
std::string_view error{e.what()};
211-
KJ_EXPECT(error.starts_with("kj::Exception: "));
212-
KJ_EXPECT(error.find("disconnected: Peer disconnected.") != std::string_view::npos);
209+
} catch (const DisconnectError& e) {
210+
KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
213211
disconnected = true;
214212
}
215213
KJ_EXPECT(disconnected);

0 commit comments

Comments
 (0)