diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index 0abef89..f1d783c 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -454,6 +454,8 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli : m_client(std::move(client)), m_context(connection) { + MP_LOG(*m_context.loop, Log::Info) << "Creating " << CxxTypeName(*this) << " " << this; + // Handler for the connection getting destroyed before this client object. auto disconnect_cb = m_context.connection->addSyncCleanup([this]() { // Release client capability by move-assigning to temporary. @@ -510,13 +512,16 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli template ProxyClientBase::~ProxyClientBase() noexcept { + MP_LOG(*m_context.loop, Log::Info) << "Cleaning up " << CxxTypeName(*this) << " " << this; CleanupRun(m_context.cleanup_fns); + MP_LOG(*m_context.loop, Log::Info) << "Destroying " << CxxTypeName(*this) << " " << this; } template ProxyServerBase::ProxyServerBase(std::shared_ptr impl, Connection& connection) : m_impl(std::move(impl)), m_context(&connection) { + MP_LOG(*m_context.loop, Log::Info) << "Creating " << CxxTypeName(*this) << " " << this; assert(m_impl); } @@ -535,6 +540,7 @@ ProxyServerBase::ProxyServerBase(std::shared_ptr impl, Co template ProxyServerBase::~ProxyServerBase() { + MP_LOG(*m_context.loop, Log::Info) << "Cleaning up " << CxxTypeName(*this) << " " << this; if (m_impl) { // If impl is non-null at this point, it means no client is waiting for // the m_impl server object to be destroyed synchronously. This can @@ -561,6 +567,7 @@ ProxyServerBase::~ProxyServerBase() }); } assert(m_context.cleanup_fns.empty()); + MP_LOG(*m_context.loop, Log::Info) << "Destroying " << CxxTypeName(*this) << " " << this; } //! If the capnp interface defined a special "destroy" method, as described the diff --git a/include/mp/proxy-types.h b/include/mp/proxy-types.h index 22468b1..7b1cb92 100644 --- a/include/mp/proxy-types.h +++ b/include/mp/proxy-types.h @@ -568,16 +568,16 @@ template void clientDestroy(Client& client) { if (client.m_context.connection) { - MP_LOG(*client.m_context.loop, Log::Info) << "IPC client destroy " << typeid(client).name(); + MP_LOG(*client.m_context.loop, Log::Info) << "IPC client destroy " << CxxTypeName(client); } else { - KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name()); + KJ_LOG(INFO, "IPC interrupted client destroy", CxxTypeName(client)); } } template void serverDestroy(Server& server) { - MP_LOG(*server.m_context.loop, Log::Info) << "IPC server destroy " << typeid(server).name(); + MP_LOG(*server.m_context.loop, Log::Info) << "IPC server destroy " << CxxTypeName(server); } //! Entry point called by generated client code that looks like: @@ -726,6 +726,13 @@ kj::Promise serverInvoke(Server& server, CallContext& call_context, Fn fn) MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName(); MP_LOG(*server.m_context.loop, Log::Trace) << "response data: " << LogEscape(call_context.getResults().toString(), server.m_context.loop->m_log_opts.max_chars); + }, [&server, req](::kj::Exception&& e) { + // Call failed for some reason. Cap'n Proto will try to send + // this error to the client as well, but it is good to log the + // failure early here and include the request number. + MP_LOG(*server.m_context.loop, Log::Error) << "IPC server error request #" << req << " " << TypeName() + << " " << kj::str("kj::Exception: ", e).cStr(); + return kj::mv(e); }); } catch (const std::exception& e) { MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception: " << e.what(); diff --git a/include/mp/type-context.h b/include/mp/type-context.h index 09ac179..e66af6e 100644 --- a/include/mp/type-context.h +++ b/include/mp/type-context.h @@ -163,6 +163,40 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& << "IPC server error request #" << req << ", missing thread to execute request"; throw std::runtime_error("invalid thread handle"); } + }, [&server, req](::kj::Exception&& e) { + // If you see the error "(remote):0: failed: remote exception: + // Called null capability" here, it probably means your Init class + // is missing a declaration like: + // + // construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); + // + // which passes a ThreadMap reference from the client to the server, + // allowing the server to create threads to run IPC calls on the + // client, and also returns a ThreadMap reference from the server to + // the client, allowing the client to create threads on the server. + // (Typically the latter ThreadMap is used more often because there + // are more client-to-server calls.) + // + // If the other side of the connection did not previously get a + // ThreadMap reference from this side of the connection, when the + // other side calls `m_thread_map.makeThreadRequest()` in + // `BuildField` above, `m_thread_map` will be null, but that call + // will not fail immediately due to Cap'n Proto's request pipelining + // and delayed execution. Instead that call will return an invalid + // Thread reference, and when that reference is passed to this side + // of the connection as `thread_client` above, the + // `getLocalServer(thread_client)` call there will be the first + // thing to overtly fail, leading to an error here. + // + // Potentially there are also other things that could cause errors + // here, but this is the most likely cause. + // + // The log statement here is not strictly necessary since the same + // exception will also be logged in serverInvoke, but this logging + // may provide extra context that could be helpful for debugging. + MP_LOG(*server.m_context.loop, Log::Info) + << "IPC server error request #" << req << " CapabilityServerSet::getLocalServer call failed, did you forget to provide a ThreadMap to the client prior to this IPC call?"; + return kj::mv(e); }) // Wait for the invocation to finish before returning to the caller. .then([invoke_wait = kj::mv(future.promise)]() mutable { return kj::mv(invoke_wait); }); diff --git a/include/mp/util.h b/include/mp/util.h index e5b4dd1..43f17eb 100644 --- a/include/mp/util.h +++ b/include/mp/util.h @@ -7,18 +7,24 @@ #include #include -#include +#include #include #include #include #include #include #include +#include #include #include #include #include +#if __has_include() +#include +#include +#endif + namespace mp { //! Generic utility functions used by capnp code. @@ -238,6 +244,28 @@ inline char* CharCast(unsigned char* c) { return (char*)c; } inline const char* CharCast(const char* c) { return c; } inline const char* CharCast(const unsigned char* c) { return (const char*)c; } +#if __has_include() // GCC & Clang ─ use to demangle +inline std::string _demangle(const char* m) +{ + int status = 0; + std::unique_ptr p{ + abi::__cxa_demangle(m, nullptr, nullptr, &status), std::free}; + return (status == 0 && p) ? p.get() : m; // fall back on mangled if needed +} +#else // MSVC or other ─ no demangling available +inline std::string _demangle(const char* m) { return m; } +#endif + +template +std::string CxxTypeName(const T& /*unused*/) +{ +#ifdef __cpp_rtti + return _demangle(typeid(std::decay_t).name()); +#else + return ""; +#endif +} + } // namespace mp #endif // MP_UTIL_H diff --git a/src/mp/gen.cpp b/src/mp/gen.cpp index 1840071..26862f3 100644 --- a/src/mp/gen.cpp +++ b/src/mp/gen.cpp @@ -238,8 +238,11 @@ static void Generate(kj::StringPtr src_prefix, cpp_types << "// Generated by " PROXY_BIN " from " << src_file << "\n\n"; cpp_types << "// IWYU pragma: no_include \"mp/proxy.h\"\n"; cpp_types << "// IWYU pragma: no_include \"mp/proxy-io.h\"\n"; + cpp_types << "#include <" << include_path << ".h> // IWYU pragma: keep\n"; cpp_types << "#include <" << include_path << ".proxy.h>\n"; cpp_types << "#include <" << include_path << ".proxy-types.h> // IWYU pragma: keep\n"; + cpp_types << "#include \n"; + cpp_types << "#include \n"; cpp_types << "#include <" << PROXY_TYPES << ">\n\n"; cpp_types << "namespace mp {\n";