Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/mp/proxy-io.h
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ ProxyClientBase<Interface, Impl>::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.
Expand Down Expand Up @@ -551,13 +553,16 @@ ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client cli
template <typename Interface, typename Impl>
ProxyClientBase<Interface, Impl>::~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 <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> 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);
}

Expand All @@ -576,6 +581,7 @@ ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Co
template <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::~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
Expand All @@ -602,6 +608,7 @@ ProxyServerBase<Interface, Impl>::~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
Expand Down
16 changes: 12 additions & 4 deletions include/mp/proxy-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,16 +595,16 @@ template <typename Client>
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 <typename Server>
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:
Expand Down Expand Up @@ -740,6 +740,7 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
using ServerContext = ServerInvokeContext<Server, CallContext>;
using ArgList = typename ProxyClientMethodTraits<typename Params::Reads>::Params;
ServerContext server_context{server, call_context, req};
auto self = server.thisCap();
// ReplaceVoid is used to support fn.invoke implementations that
// execute asynchronously and return promises, as well as
// implementations that execute synchronously and return void. The
Expand All @@ -749,10 +750,17 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
// and waiting for it to complete.
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
.then([&server, req](CallContext call_context) {
.then([&server, req, self](CallContext call_context) {
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName<Results>();
MP_LOG(*server.m_context.loop, Log::Trace) << "response data: "
<< LogEscape(call_context.getResults().toString(), server.m_context.loop->m_log_opts.max_chars);
}).catch_([&server, req, self](::kj::Exception&& e) -> kj::Promise<void> {
// 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<Results>()
<< " " << kj::str("kj::Exception: ", e.getDescription()).cStr();
return kj::mv(e);
});
} catch (const std::exception& e) {
MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception: " << e.what();
Expand Down
34 changes: 34 additions & 0 deletions include/mp/type-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,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) -> kj::Promise<typename ServerContext::CallContext> {
// 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<Thread>::getLocalServer call failed, did you forget to provide a ThreadMap to the client prior to this IPC call?";
return kj::mv(e);
});
// Use connection m_canceler object to cancel the result promise if the
// connection is destroyed. (By default Cap'n Proto does not cancel requests
Expand Down
31 changes: 30 additions & 1 deletion include/mp/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@

#include <capnp/schema.h>
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <functional>
#include <kj/string-tree.h>
#include <mutex>
#include <string>
#include <tuple>
#include <typeinfo>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#if __has_include(<cxxabi.h>)
#include <cxxabi.h>
#include <memory>
#endif

namespace mp {

//! Generic utility functions used by capnp code.
Expand Down Expand Up @@ -274,6 +280,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(<cxxabi.h>) // GCC & Clang ─ use <cxxabi.h> to demangle
inline std::string _demangle(const char* m)
{
int status = 0;
std::unique_ptr<char, void(*)(void*)> 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<class T>
std::string CxxTypeName(const T& /*unused*/)
{
Copy link
Contributor

@theuni theuni Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is only done in one place now, maybe wrap this in (c++20 standard) #ifdef __cpp_rtti? I checked quickly, and gcc and clang treat that define as-expected with or with or without -fno-rtti.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #218 (comment)

Sure, added #ifdef __cpp_rtti, though have not tested without rtti myself.

#ifdef __cpp_rtti
return _demangle(typeid(std::decay_t<T>).name());
#else
return "<type information unavailable without rtti>";
#endif
}

//! Exception thrown from code executing an IPC call that is interrupted.
struct InterruptException final : std::exception {
explicit InterruptException(std::string message) : m_message(std::move(message)) {}
Expand Down Expand Up @@ -337,6 +365,7 @@ void CancelMonitor::promiseDestroyed(CancelProbe& probe)
if (m_on_cancel) m_on_cancel();
m_probe = nullptr;
}

} // namespace mp

#endif // MP_UTIL_H
3 changes: 3 additions & 0 deletions src/mp/gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,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 <kj/common.h>\n";
cpp_types << "#include <mp/util.h>\n";
cpp_types << "#include <" << PROXY_TYPES << ">\n\n";
cpp_types << "namespace mp {\n";

Expand Down
Loading