mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-31 05:51:08 +00:00
#40 made lp_invoke_async able to report a failure, but only for the two conditions produced ABOVE the transport: acquire failure and the unauthorized sentinel. Everything the transport learns while the call is in flight was still discarded — PlainLogosObject answered a bare QVariant() for a timeout and for `ResultMessage.ok == false` alike, and LogosAPIConsumer hard-coded an empty CallError next to it. Two ordinary failures therefore still reported success on both entry points: a TIMEOUT, and MODULE NOT LOADED against a host that is up (which is not an acquire failure on the plain wire — requestObject hands back a handle for any name over an open connection). The information already exists: ResultMessage carries err/errCode, the futures know they expired, QtRO knows its pending call never finished. It had nowhere to go because LogosObject's callMethod returns a lone QVariant and its callMethodAsync callback takes a lone QVariant. Widening those virtuals would append a vtable slot to an installed, subclassed interface, so instead this adds LogosObjectErrorChannel — a SIBLING interface reached by dynamic_cast. LogosObject's size, layout and vtable are unchanged (verified: a subclass compiled against the old and new headers emits the same 14-entry vtable with identical slot indices), and a transport that does not implement it keeps today's behaviour. logos_protocol.cpp needs no change: lp_invoke and lp_invoke_async already render this CallError, so both twins gain the coverage together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
4.3 KiB
C++
100 lines
4.3 KiB
C++
#ifndef LOGOS_PLAIN_LOGOS_OBJECT_H
|
|
#define LOGOS_PLAIN_LOGOS_OBJECT_H
|
|
|
|
#include "logos_object.h"
|
|
|
|
#include "rpc_connection.h"
|
|
|
|
#include <condition_variable>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace logos::plain {
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PlainLogosObject — consumer-side LogosObject backed by the plain-C++
|
|
// RPC runtime. Identical public shape to LocalLogosObject / RemoteLogosObject
|
|
// so LogosAPIConsumer doesn't care which backend it's talking to.
|
|
//
|
|
// Owns a shared_ptr<RpcConnectionBase>; the transport layer hands the
|
|
// connection over after opening the socket. release() stops the connection.
|
|
// -----------------------------------------------------------------------------
|
|
class PlainLogosObject : public LogosObject, public LogosObjectErrorChannel {
|
|
public:
|
|
PlainLogosObject(std::string objectName,
|
|
std::shared_ptr<RpcConnectionBase> conn);
|
|
~PlainLogosObject() override;
|
|
|
|
QVariant callMethod(const QString& authToken,
|
|
const QString& methodName,
|
|
const QVariantList& args,
|
|
int timeoutMs) override;
|
|
|
|
void callMethodAsync(const QString& authToken,
|
|
const QString& methodName,
|
|
const QVariantList& args,
|
|
int timeoutMs,
|
|
AsyncResultCallback callback) override;
|
|
|
|
// LogosObjectErrorChannel — the real implementations. The two LogosObject
|
|
// entry points above are thin adapters that discard the error, so there is
|
|
// exactly ONE call path per direction and the two front doors cannot drift.
|
|
QVariant callMethodWithError(const QString& authToken,
|
|
const QString& methodName,
|
|
const QVariantList& args,
|
|
int timeoutMs,
|
|
logos::CallError* err) override;
|
|
|
|
void callMethodAsyncWithError(const QString& authToken,
|
|
const QString& methodName,
|
|
const QVariantList& args,
|
|
int timeoutMs,
|
|
AsyncResultErrorCallback callback) override;
|
|
|
|
bool informModuleToken(const QString& authToken,
|
|
const QString& moduleName,
|
|
const QString& token,
|
|
int timeoutMs) override;
|
|
|
|
void onEvent(const QString& eventName, EventCallback callback) override;
|
|
void disconnectEvents() override;
|
|
void emitEvent(const QString& eventName, const QVariantList& data) override;
|
|
QJsonArray getMethods() override;
|
|
void release() override;
|
|
quintptr id() const override;
|
|
|
|
private:
|
|
// Deferred ("multi") completion rendezvous. A multi provider returns a
|
|
// pending sentinel (logos::pendingCallKey) from callMethod and later pushes
|
|
// the real result as a logos::callCompleteEvent event keyed by callId. We
|
|
// subscribe to that event EAGERLY (before any call can defer) so a completion
|
|
// racing ahead of the waiter is buffered, then block the caller until the
|
|
// matching callId lands. The completion arrives on the connection's IO
|
|
// thread; the caller waits on another thread — m_completionMu/Cv bridge them.
|
|
void ensureCompletionSub();
|
|
// `err` (optional) receives the timeout when the completion never lands —
|
|
// a deferred call that gives up is a timeout like any other, and used to be
|
|
// reported as a null result.
|
|
QVariant awaitCompletion(const QString& callId, int timeoutMs,
|
|
const QString& methodName = QString(),
|
|
logos::CallError* err = nullptr);
|
|
|
|
std::string m_objectName;
|
|
std::shared_ptr<RpcConnectionBase> m_conn;
|
|
std::mutex m_mu;
|
|
std::vector<std::pair<QString, EventCallback>> m_subs;
|
|
|
|
std::mutex m_completionMu;
|
|
std::condition_variable m_completionCv;
|
|
std::map<QString, QVariant> m_completions;
|
|
bool m_completionSubscribed = false;
|
|
};
|
|
|
|
} // namespace logos::plain
|
|
|
|
#endif // LOGOS_PLAIN_LOGOS_OBJECT_H
|