mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-30 21:41:10 +00:00
fix(lp): lp_invoke_async can finally report a failure (#40)
lp_result_cb has always been documented as carrying an outcome —
"ok != 0 -> `json` is the result JSON value; ok == 0 -> `json` is the
canonical error object" — and the synchronous twin lp_invoke has always
honoured it (LP_ERR_UNAVAILABLE + out_error_json). lp_invoke_async did
not: it subscribed with the VALUE-ONLY invokeRemoteMethodAsync overload
and called back `cb(1, json, user_data)` with ok hard-coded to 1, so a
call to a module that cannot be acquired reached the callback as a
SUCCESS carrying a default-constructed value.
The fix is to pass a TWO-argument lambda, which is invocable only as
LogosAPIClient::AsyncResultErrorCallback and so binds to the
CallError-aware overload that already exists next to the value-only one.
The failure is then rendered with the same makeErrorJson the sync path
uses, so both entry points report the same event in the same shape.
The ABI is unchanged. lp_result_cb's (ok, json, user_data) signature
already reserves ok == 0 for exactly this; nothing had to grow a new
entry point, and every in-tree consumer already branches on `ok`
(logos-rust-sdk's async_call_trampoline even parses `message` out of the
canonical error object — code written against a contract the
implementation never kept).
Regression test: a matched pair over a REAL transport (plain TCP), not
the mock.
FAILING async call -> ok=0 {"code":"object_unavailable", ...}
SUCCEEDING async call -> ok=1 7
The first fails on the unfixed tree (ok=1, json "null"); the second
passes on both, so an over-eager "report failure everywhere" fix cannot
sneak through.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3a31c91d13
commit
d0523c1486
+21
-1
@@ -1,6 +1,7 @@
|
||||
#include "logos_protocol.h"
|
||||
|
||||
#include "logos_api_client.h"
|
||||
#include "logos_call_error.h"
|
||||
#include "logos_json_convert.h"
|
||||
#include "logos_mode.h"
|
||||
#include "logos_object.h"
|
||||
@@ -338,11 +339,30 @@ int lp_invoke_async(lp_client* client,
|
||||
return LP_ERR_INVALID_ARG;
|
||||
|
||||
std::shared_ptr<CbGuard> guard = client->guard;
|
||||
// A TWO-argument lambda: invocable only as LogosAPIClient's
|
||||
// AsyncResultErrorCallback, so it binds to the CallError-aware overload and
|
||||
// never to the value-only one sitting next to it. That overload is what
|
||||
// makes `ok == 0` reachable at all — this used to subscribe with the
|
||||
// value-only one and hard-code cb(1, ...), so a call to a module that is
|
||||
// not loaded reached the callback as a SUCCESS carrying a default value,
|
||||
// contradicting both lp_result_cb's documented contract and the sync twin
|
||||
// lp_invoke (which returns LP_ERR_UNAVAILABLE + out_error_json).
|
||||
//
|
||||
// The failure shape is deliberately identical to lp_invoke's
|
||||
// out_error_json — the same makeErrorJson({code, message, origin}) — so the
|
||||
// two entry points report the same event the same way, and a caller can
|
||||
// parse one decoder for both.
|
||||
client->client->invokeRemoteMethodAsync(
|
||||
client->target, QString::fromUtf8(method), args,
|
||||
[guard, cb, user_data](QVariant result) {
|
||||
[guard, cb, user_data](QVariant result, const logos::CallError& err) {
|
||||
std::lock_guard<std::recursive_mutex> lock(guard->mutex);
|
||||
if (!guard->alive) return; // client destroyed: drop the result
|
||||
if (!err.ok()) {
|
||||
const std::string json = makeErrorJson(err.code.c_str(),
|
||||
err.message, err.origin);
|
||||
cb(0, json.c_str(), user_data);
|
||||
return;
|
||||
}
|
||||
const std::string json = logos::qvariantToNlohmann(result).dump();
|
||||
cb(1, json.c_str(), user_data);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user