mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-27 12:01:15 +00:00
lp_client_create() made the CALLING thread the client's owner thread. Callers reach it through a lazily-created wrapper (the generated bind_<iface>() -> LpClient::ensure()), so the first thread to make an outbound call captured the whole transport for the life of the process. For the qt_remote transport that thread also ends up owning the QRemoteObjectNode and its QLocalSocket, which are only serviced by a thread running a Qt event loop. A module whose first call came from a worker — an HTTP handler, a timer thread — bound its transport to a thread that only pumps events while it is already blocked inside a call. Replica acquisition then never completed: every requestObject() burned its full 20s timeout and returned nullptr, and since a failed acquire yields an empty result the data loss was silent. openmetrics-module hit exactly this: one GET /metrics took 40s (2 x 20s) and came back missing a module, /health went unanswered behind the wedged libmicrohttpd thread, and the follow-up stop RPC failed. Construct the client on the Qt main thread when the transport needs a Qt event loop, so the per-call marshal that already exists (logos::runOnOwnerThread) lands somewhere that can actually service it. This is the anchor the Qt path always had — LogosAPI::getClient marshals construction to the LogosAPI's thread — given to the lp path. Plain (tcp/tcp_ssl) and mock transports are Qt-free and thread-agnostic, so they keep the calling thread: a worker-thread consumer stays off the main thread's back. LogosTransportFactory::needsQtEventLoop() carries that rule next to the createConnection resolution it mirrors. When there is nothing to anchor to (a Qt-affine transport with no QCoreApplication) we now warn instead of letting it surface as a mute timeout. Tests: a worker thread creates an lp client over qt_remote and calls a provider published on the main thread; passes in ~0.15s, and with the construction hop reverted fails after 24.8s / 49.9s — the acquire timeouts themselves. Plus a truth table for needsQtEventLoop. 183/183 protocol tests pass. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
77 lines
2.9 KiB
C++
77 lines
2.9 KiB
C++
#ifndef LOGOS_TRANSPORT_FACTORY_H
|
|
#define LOGOS_TRANSPORT_FACTORY_H
|
|
|
|
#include "logos_transport_config.h"
|
|
|
|
#include <memory>
|
|
#include <QString>
|
|
|
|
class LogosTransportHost;
|
|
class LogosTransportConnection;
|
|
|
|
namespace LogosTransportFactory {
|
|
|
|
/**
|
|
* @brief Create a transport host for `cfg`, honoring the process-wide
|
|
* LogosMode.
|
|
*
|
|
* Resolution rule:
|
|
* - LogosMode::Mock → MockTransportHost (cfg ignored)
|
|
* - LogosMode::Local → LocalTransportHost (cfg ignored)
|
|
* - LogosMode::Remote + LocalSocket → RemoteTransportHost (QRO)
|
|
* - LogosMode::Remote + Tcp/TcpSsl → PlainTransportHost(cfg)
|
|
*
|
|
* Mode wins over `cfg.protocol` so test fixtures that switch the
|
|
* process into Mock/Local always get the test transport, regardless
|
|
* of which overload (or which LogosAPIProvider constructor) was
|
|
* used. In Remote mode, `cfg` chooses the wire protocol and
|
|
* carries the bind/dial address + TLS material.
|
|
*/
|
|
std::unique_ptr<LogosTransportHost>
|
|
createHost(const LogosTransportConfig& cfg,
|
|
const QString& registryUrl);
|
|
|
|
/**
|
|
* @brief Convenience: createHost using the process-global default
|
|
* LogosTransportConfig. Equivalent to
|
|
* `createHost(LogosTransportConfigGlobal::getDefault(), registryUrl)`.
|
|
*/
|
|
std::unique_ptr<LogosTransportHost> createHost(const QString& registryUrl);
|
|
|
|
/**
|
|
* @brief Create a transport connection for `cfg`, honoring the
|
|
* process-wide LogosMode. Same resolution rule as createHost — see
|
|
* its doc-comment for the full table.
|
|
*/
|
|
std::unique_ptr<LogosTransportConnection>
|
|
createConnection(const LogosTransportConfig& cfg,
|
|
const QString& registryUrl);
|
|
|
|
/**
|
|
* @brief Convenience: createConnection using the process-global default
|
|
* LogosTransportConfig. Equivalent to
|
|
* `createConnection(LogosTransportConfigGlobal::getDefault(), registryUrl)`.
|
|
*/
|
|
std::unique_ptr<LogosTransportConnection> createConnection(const QString& registryUrl);
|
|
|
|
/**
|
|
* @brief Whether a connection resolved from `cfg` owns Qt objects that only
|
|
* work on a thread running a Qt event loop.
|
|
*
|
|
* True for the qt_remote (LocalSocket) transport — a QRemoteObjectNode plus
|
|
* its QLocalSocket, whose replica acquisition and reply delivery both ride
|
|
* the owning thread's event loop — and for the in-process qt_local
|
|
* transport, which invokes on QObjects living on the module's main thread.
|
|
* False for the plain Tcp/TcpSsl transports (Qt-free by design) and for
|
|
* Mock (no sockets at all).
|
|
*
|
|
* Callers use this to decide *which thread must construct* a client: see
|
|
* lp_client_create(). Mirrors the createConnection resolution rule above —
|
|
* keep the two in sync.
|
|
*/
|
|
bool needsQtEventLoop(const LogosTransportConfig& cfg);
|
|
|
|
}
|
|
|
|
#endif // LOGOS_TRANSPORT_FACTORY_H
|