mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-30 09:11:13 +00:00
* feat(lp): emit the AsyncResult twin, and fix the LpClient create race
Two related changes to the Qt-free (ApiStyle::Lp) consumer surface.
1. logos::LpClient::ensure() published its lazily-created lp_client through a
plain pointer with no synchronization. Two threads reach a dep's FIRST call
concurrently more often than the lazy-init shape suggests: a
concurrency:"multi" module dispatches handlers on concurrent QThreads, and
any module running a worker of its own (an HTTP handler, a chain-sync pump)
races that worker against the dispatch thread. So this was a data race, and
it leaked whichever client lost.
A mutex around the body is the obvious fix and the wrong one: for a Qt-affine
transport lp_client_create marshals construction onto the Qt main thread and
BLOCKS there, so a worker holding the lock would wait for the main thread
while the main thread, reaching the same ensure() from an inbound call, waits
for the lock — trading a data race for a deadlock. Construct outside any lock
and publish with a CAS instead; the loser destroys its own client, which
lp_client_destroy permits from any thread. A failed create is not latched.
2. `<name>AsyncResult` is now emitted for the lp surface, matching the Qt one.
It was withheld for a reason that belonged to the transport rather than the
emitter: lp_invoke_async used to hard-code `cb(1, ...)`, so an AsyncResult
over it would have reported ok() for a call to a module that was not even
loaded — an error channel that lies is worse than none. logos-protocol#40
fixed that, and the new logos::LpClient::invokeAsyncResult surfaces the
failure in C++.
The generated twin also folds a provider REJECTION: a provider that ran and
refused answers {"code": "dispatch_failed", ...} as its RESULT, not as a
transport error, so the decode would otherwise erase it into a default value.
The lp SYNC path folds it too now, as the Qt sync path already did — with no
qWarning fallback, since a Qt-free wrapper has no logger to fall back to.
lp `<name>Async` is deliberately unchanged: its callback takes the value
alone, exactly as on the Qt side.
Also unblocks logos-qt-sdk's LpBridge::invokeAsyncResult, which keeps a private
second lp_client only because logos::LpClient had no error-carrying async.
Verified: sdk tests 309/309 (incl. 9 new LpClient and 10 new generator tests),
generator-cli green, and the generated wrapper compiles under -Wall -Wextra
-Werror. The concurrency test was checked against the pre-fix header as a
control: 8 threads, 8 clients created, 7 leaked, threads disagreeing on which
client was the module's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(tests): stub lp_string_free, and keep the path that needs it live
The sdk_tests link failed on GCC/Linux with an undefined reference to
lp_string_free from logos::LpClient::getMethods(), while linking cleanly on
macOS/clang.
The stub set was genuinely missing lp_string_free. It went unnoticed because the
lp_get_methods stub returned NULL, which made getMethods()'s free call
unreachable: lp_get_methods is a non-inline extern "C" function DEFINED IN THE
SAME TU as the test, so clang may inline it, prove the pointer null and delete
the call — no reference, no link error. GCC kept the call, and the linker wanted
the symbol.
Adding the stub alone would fix the link and leave the trap: the free path would
still be dead, so the next compiler that keeps the call decides whether this
builds. So lp_get_methods now returns a real heap allocation, which is the ABI's
actual contract ("every char* RETURNED by this library is owned by the caller;
free it with lp_string_free"), and lp_string_free frees it and counts. The
single-threaded test asserts the count, so the ownership rule is pinned rather
than merely satisfied.
Verified by reproducing the failure on macOS with -O0 (which stops clang folding
the call away): the pre-fix file fails with exactly `"_lp_string_free",
referenced from: logos::LpClient::getMethods()`, and the fixed one links and
runs 9/9. Full check: 308/308.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>