Skip to content

Commit 2ac55a5

Browse files
committed
Merge #218: Better error and log messages
4eae445 debug: Add TypeName() function and log statements for Proxy objects being created and destroyed (Ryan Ofsky) f326c5b logging: Add better logging on IPC server-side failures (Ryan Ofsky) Pull request description: This PR contains some logging and error message improvements I made locally in recent months that seem useful to upstream. Details are in commit messages ACKs for top commit: sedited: ACK 4eae445 Tree-SHA512: 8eaa83df2d9181de56fea315b4cdbf04d5f714862d8c74394b16cd9e8f74cd1bba4846794ae1e29abc0d9df0e7503276a7196cf1d10d49a79bc897264b17906f
2 parents 3c69d12 + 4eae445 commit 2ac55a5

5 files changed

Lines changed: 92 additions & 13 deletions

File tree

include/mp/proxy-io.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client cli
511511
: m_client(std::move(client)), m_context(connection)
512512

513513
{
514+
MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this;
514515
// Handler for the connection getting destroyed before this client object.
515516
auto disconnect_cb = m_context.connection->addSyncCleanup([this]() {
516517
// Release client capability by move-assigning to temporary.
@@ -567,13 +568,16 @@ ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client cli
567568
template <typename Interface, typename Impl>
568569
ProxyClientBase<Interface, Impl>::~ProxyClientBase() noexcept
569570
{
571+
MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this;
570572
CleanupRun(m_context.cleanup_fns);
573+
MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this;
571574
}
572575

573576
template <typename Interface, typename Impl>
574577
ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection)
575578
: m_impl(std::move(impl)), m_context(&connection)
576579
{
580+
MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this;
577581
assert(m_impl);
578582
}
579583

@@ -592,6 +596,7 @@ ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Co
592596
template <typename Interface, typename Impl>
593597
ProxyServerBase<Interface, Impl>::~ProxyServerBase()
594598
{
599+
MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this;
595600
if (m_impl) {
596601
// If impl is non-null at this point, it means no client is waiting for
597602
// the m_impl server object to be destroyed synchronously. This can
@@ -618,6 +623,7 @@ ProxyServerBase<Interface, Impl>::~ProxyServerBase()
618623
});
619624
}
620625
assert(m_context.cleanup_fns.empty());
626+
MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this;
621627
}
622628

623629
//! If the capnp interface defined a special "destroy" method, as described the

include/mp/proxy-types.h

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -645,16 +645,16 @@ template <typename Client>
645645
void clientDestroy(Client& client)
646646
{
647647
if (client.m_context.connection) {
648-
MP_LOG(*client.m_context.loop, Log::Debug) << "IPC client destroy " << typeid(client).name();
648+
MP_LOG(*client.m_context.loop, Log::Debug) << "IPC client destroy " << CxxTypeName(client);
649649
} else {
650-
KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name());
650+
KJ_LOG(INFO, "IPC interrupted client destroy", CxxTypeName(client));
651651
}
652652
}
653653

654654
template <typename Server>
655655
void serverDestroy(Server& server)
656656
{
657-
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server destroy " << typeid(server).name();
657+
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server destroy " << CxxTypeName(server);
658658
}
659659

660660
//! Entry point called by generated client code that looks like:
@@ -780,10 +780,11 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
780780
using Params = decltype(params);
781781
using Results = typename decltype(call_context.getResults())::Builds;
782782

783+
EventLoop& loop = *server.m_context.loop;
783784
int req = ++server_reqs;
784-
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server recv request #" << req << " "
785-
<< TypeName<typename Params::Reads>();
786-
MP_LOG(*server.m_context.loop, Log::Trace) << "request data: "
785+
MP_LOG(loop, Log::Debug) << "IPC server recv request #" << req << " "
786+
<< TypeName<typename Params::Reads>();
787+
MP_LOG(loop, Log::Trace) << "request data: "
787788
<< LogEscape(params.toString(), server.m_context.loop->m_log_opts.max_chars);
788789

789790
try {
@@ -799,16 +800,23 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
799800
// and waiting for it to complete.
800801
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
801802
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
802-
.then([&server, req](CallContext call_context) {
803-
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName<Results>();
804-
MP_LOG(*server.m_context.loop, Log::Trace) << "response data: "
805-
<< LogEscape(call_context.getResults().toString(), server.m_context.loop->m_log_opts.max_chars);
803+
.then([&loop, req](CallContext call_context) {
804+
MP_LOG(loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName<Results>();
805+
MP_LOG(loop, Log::Trace) << "response data: "
806+
<< LogEscape(call_context.getResults().toString(), loop.m_log_opts.max_chars);
807+
}).catch_([&loop, req](::kj::Exception&& e) -> kj::Promise<void> {
808+
// Call failed for some reason. Cap'n Proto will try to send
809+
// this error to the client as well, but it is good to log the
810+
// failure early here and include the request number.
811+
MP_LOG(loop, Log::Error) << "IPC server error request #" << req << " " << TypeName<Results>()
812+
<< " " << kj::str("kj::Exception: ", e.getDescription()).cStr();
813+
return kj::mv(e);
806814
});
807815
} catch (const std::exception& e) {
808-
MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception: " << e.what();
816+
MP_LOG(loop, Log::Error) << "IPC server unhandled exception: " << e.what();
809817
throw;
810818
} catch (...) {
811-
MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception";
819+
MP_LOG(loop, Log::Error) << "IPC server unhandled exception";
812820
throw;
813821
}
814822
}

include/mp/type-context.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,40 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
216216
<< "IPC server error request #" << req << ", missing thread to execute request";
217217
throw std::runtime_error("invalid thread handle");
218218
}
219+
}, [&loop, req](::kj::Exception&& e) -> kj::Promise<typename ServerContext::CallContext> {
220+
// If you see the error "(remote):0: failed: remote exception:
221+
// Called null capability" here, it probably means your Init class
222+
// is missing a declaration like:
223+
//
224+
// construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
225+
//
226+
// which passes a ThreadMap reference from the client to the server,
227+
// allowing the server to create threads to run IPC calls on the
228+
// client, and also returns a ThreadMap reference from the server to
229+
// the client, allowing the client to create threads on the server.
230+
// (Typically the latter ThreadMap is used more often because there
231+
// are more client-to-server calls.)
232+
//
233+
// If the other side of the connection did not previously get a
234+
// ThreadMap reference from this side of the connection, when the
235+
// other side calls `m_thread_map.makeThreadRequest()` in
236+
// `BuildField` above, `m_thread_map` will be null, but that call
237+
// will not fail immediately due to Cap'n Proto's request pipelining
238+
// and delayed execution. Instead that call will return an invalid
239+
// Thread reference, and when that reference is passed to this side
240+
// of the connection as `thread_client` above, the
241+
// `getLocalServer(thread_client)` call there will be the first
242+
// thing to overtly fail, leading to an error here.
243+
//
244+
// Potentially there are also other things that could cause errors
245+
// here, but this is the most likely cause.
246+
//
247+
// The log statement here is not strictly necessary since the same
248+
// exception will also be logged in serverInvoke, but this logging
249+
// may provide extra context that could be helpful for debugging.
250+
MP_LOG(loop, Log::Info)
251+
<< "IPC server error request #" << req << " CapabilityServerSet<Thread>::getLocalServer call failed, did you forget to provide a ThreadMap to the client prior to this IPC call?";
252+
return kj::mv(e);
219253
});
220254
// Use connection m_canceler object to cancel the result promise if the
221255
// connection is destroyed. (By default Cap'n Proto does not cancel requests

include/mp/util.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@
77

88
#include <capnp/schema.h>
99
#include <cassert>
10-
#include <cstddef>
10+
#include <cstdlib>
1111
#include <cstring>
1212
#include <exception>
1313
#include <functional>
1414
#include <kj/string-tree.h>
1515
#include <mutex>
1616
#include <string>
1717
#include <tuple>
18+
#include <typeinfo>
1819
#include <type_traits>
1920
#include <utility>
2021
#include <variant>
2122
#include <vector>
2223

24+
#if __has_include(<cxxabi.h>)
25+
#include <cxxabi.h>
26+
#include <memory>
27+
#endif
28+
2329
namespace mp {
2430

2531
//! Generic utility functions used by capnp code.
@@ -274,6 +280,28 @@ inline char* CharCast(unsigned char* c) { return (char*)c; }
274280
inline const char* CharCast(const char* c) { return c; }
275281
inline const char* CharCast(const unsigned char* c) { return (const char*)c; }
276282

283+
#if __has_include(<cxxabi.h>) // GCC & Clang ─ use <cxxabi.h> to demangle
284+
inline std::string _demangle(const char* m)
285+
{
286+
int status = 0;
287+
std::unique_ptr<char, void(*)(void*)> p{
288+
abi::__cxa_demangle(m, /*output_buffer=*/nullptr, /*length=*/nullptr, &status), std::free};
289+
return (status == 0 && p) ? p.get() : m; // fall back on mangled if needed
290+
}
291+
#else // MSVC or other ─ no demangling available
292+
inline std::string _demangle(const char* m) { return m; }
293+
#endif
294+
295+
template<class T>
296+
std::string CxxTypeName(const T& /*unused*/)
297+
{
298+
#ifdef __cpp_rtti
299+
return _demangle(typeid(std::decay_t<T>).name());
300+
#else
301+
return "<type information unavailable without rtti>";
302+
#endif
303+
}
304+
277305
//! Exception thrown from code executing an IPC call that is interrupted.
278306
struct InterruptException final : std::exception {
279307
explicit InterruptException(std::string message) : m_message(std::move(message)) {}

src/mp/gen.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,11 @@ static void Generate(kj::StringPtr src_prefix,
240240
cpp_types << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
241241
cpp_types << "// IWYU pragma: no_include \"mp/proxy.h\"\n";
242242
cpp_types << "// IWYU pragma: no_include \"mp/proxy-io.h\"\n";
243+
cpp_types << "#include <" << include_path << ".h> // IWYU pragma: keep\n";
243244
cpp_types << "#include <" << include_path << ".proxy.h>\n";
244245
cpp_types << "#include <" << include_path << ".proxy-types.h> // IWYU pragma: keep\n";
246+
cpp_types << "#include <kj/common.h>\n";
247+
cpp_types << "#include <mp/util.h>\n";
245248
cpp_types << "#include <" << PROXY_TYPES << ">\n\n";
246249
cpp_types << "namespace mp {\n";
247250

0 commit comments

Comments
 (0)