mirror of
https://github.com/logos-co/logos-rust-sdk.git
synced 2026-08-27 09:51:06 +00:00
* feat(timeout): surface the per-call timeout the lp_* ABI already carried
lp_invoke/lp_invoke_async have taken timeout_ms: c_int since they existed,
and logos_protocol.cpp maps it (`timeout_ms > 0 ? Timeout(timeout_ms)
: Timeout()`). Rust passed a literal 0 at all five call sites and had no
mention of a timeout anywhere else, so every Rust module was pinned to the
20s default with no way to ask for anything else.
Exposed as a timeout-scoped VIEW rather than a parameter: Rust has no
default arguments, so an extra parameter would break every generated call
site, and a parallel `_with_timeout` entry point per method would double
the generated surface (and need a second answer for the async twin).
`with_timeout(Duration)` returns a new client over the SAME lp_client with
only the timeout different — one emission covering sync and async and every
method, with `self` left on the default so a scoped bound cannot leak.
Duration -> c_int converts in one place and REFUSES rather than clamps at
both ends: sub-millisecond would round to 0, which the ABI reads as "use
the default" (a 500us bound silently becoming 20s), and past c_int::MAX ms
truncation wraps negative — the default again. Both raise the new
LogosError::InvalidTimeout where the bad value was supplied.
Rust's error handling is deliberately untouched: it already returns Result
on both surfaces, which is the model the parallel C++ change is moving
toward.
Proved with a clock, not a getter: the provider fixture gained sleep(ms),
and tests/ipc-test.sh measures a 6s call under an 800ms timeout returning
at ~800ms on both surfaces, the same call with no timeout still completing
at ~6s, and a 25s call on the default path failing at ~20s. Restoring the
hardcoded 0 makes the check fail with "a 6s call COMPLETED under an 800ms
timeout".
The check script is extracted so flake.nix and tests/flake.nix (the CI job)
run the same assertions; they had drifted, and CI was not running the
binary-event assertions at all.
* fix(doctests): the SDK the modules link is the SDK the generator came from
The generated typed client calls into the SDK — `with_timeout` returns a
PluginProxy carrying a private field, and all five lp_invoke call sites live in
the crate — so generated code cannot be self-contained. Generator and runtime
crate have to move together.
The specs pinned them separately: the builder's SDK came from `{release}` (the
commit under test) while the crate's Cargo.toml carried a frozen `rev`. So this
PR's generator emitted `PluginProxy::with_timeout` against a runtime that
predated it — E0599, 5 failed of 44, on both platforms. The same pin had gone
stale once before on `logos_rust_sdk::args::Ty::…` (E0433), and bumping it just
resets the clock.
Both fixtures now take the SDK as `path = "../logos-rust-sdk-src"` — the form
real modules use — with the directory staged from
`logos-module-builder#rust-sdk-src` before `cargo generate-lockfile`, which is
what the frozen rev existed to work around. The lockfile-ordering constraint is
real; this satisfies it instead of sidestepping it. concurrent-dispatch stages
`{release}`, the commit under test, so there is one decision instead of two that
can drift.
cross-language-composition's two pins get the same treatment (no `{release}`
there — it stages module-builder's own pin, the published surface it
demonstrates). That is all four `rev` pins in this repo.
Also bumps the crate 0.2.0 -> 0.3.0: a 0.3 generator against a 0.2 runtime is
E0599, and `LogosError` gained `InvalidTimeout` without `#[non_exhaustive]`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(tests): the fixture lockfiles say 0.2.0 for a crate that is now 0.3.0
tests/{provider,caller}/rust-lib/Cargo.lock record logos-rust-sdk as a path dep
at 0.2.0. This is stale metadata, not breakage: buildRustPackage does not pass
--locked, so cargo re-resolves a path dep whose version moved and prints
Updating logos-rust-sdk v0.2.0 (…/logos-rust-sdk-src) -> v0.3.0
(verified in a sandbox build log). Left alone the locks still work — they would
just be rewritten by the next local `cargo build`, dirtying the tree. Bump them
so they say what the crate is.
Worth recording, because it bounds what the staged SDK source does: it seeds the
lockfile's crates.io dependency GRAPH, it does not decide which SDK source gets
compiled. The builder's own logos-rust-sdk input decides that, and it wins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(doctests): regenerate cross-language-composition outputs
The committed walkthrough output still showed `rev = "8b89e56…"`. Re-ran the
spec (21 passed, 0 failed of 21) and re-rendered the .md, so the checked-in
sources match what the spec now writes.
Also teaches run.sh's clean step about leftovers its --also list predates: the
concurrent-dispatch .lgx out-links, the staged SDK out-link (a symlink into the
nix store), and the per-spec .logoscore daemon state — none of which belong in
the committed tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(timeout): the timeout belongs to the call, not to the client
The first cut exposed the timeout as a client-scoped VIEW —
`with_timeout(Duration) -> Self` on both the proxy and the generated
client, after which every method called through the returned handle
inherited that bound. That is the wrong unit. How long a call may
reasonably take is a fact about the METHOD: `fetch_price` and
`rebuild_index` do not want the same bound, and a client-wide setting
cannot say so without building a second client.
So the generated surface is now a parallel entry point per method:
add(a, b) unchanged
add_with_timeout(a, b, timeout) new
add_async(a, b, cb) unchanged
add_async_with_timeout(a, b, timeout, cb) new
which is what shipped in C++ (logos-cpp-sdk#132's `fooAsyncResult`) —
there overloading was available and still not used, and Rust has none, so
a distinct name was always the only spelling.
This DOUBLES the generated surface and that is a deliberate stopgap, said
so in the emitted doc comments ("STOPGAP — a later breaking release folds
this back into the single entry point") so the duplication reads as
intended rather than accidental. The eventual fix is the breaking change:
`timeout` as a parameter of the one entry point per method.
`PluginProxy::with_timeout` is REMOVED rather than kept as the
implementation vehicle, along with the proxy's `timeout` field and the
`timeout()` getter. It was the rejected concept one level down: a public
API whose semantics are "bound to the client" cannot underpin a generated
surface whose semantics are "bound to the call", and anyone writing
hand-rolled proxy code would keep reaching for it. Removing the stored
field also turns "a bound cannot leak into a call that did not ask for
one" from a property the tests assert into one the type system enforces —
there is no state left to leak. And it is on the path to the breaking
change: `call_json_with_timeout` collapses into `call_json(..., timeout)`
by deleting a twin, whereas a scoped view would need separate deprecation.
In its place, each of the five `lp_invoke*` entry points grows a bounded
twin over a shared private `*_inner(..., TimeoutMs)`: `call_with_timeout`,
`call_with_params_with_timeout`, `call_sync_with_timeout`,
`call_json_with_timeout`, `call_json_async_with_timeout`. The async one
delivers `InvalidTimeout` to its callback rather than returning a Result,
so it keeps one error channel like every other undispatchable async call.
Everything else stands: `TimeoutMs::from_duration` and its refusals
(sub-millisecond would become the ABI's "use the default" sentinel, i.e.
20s; > c_int::MAX ms would wrap negative into the same), the value reaching
all five call sites, the 0.3.0 bump, and `LogosError::InvalidTimeout`. The
only casualty is `TimeoutMs::as_duration`, which existed solely to back the
deleted getter.
Verification — `tests/ipc-test.sh`, against two real cdylib modules under a
headless logoscore daemon. The caller fixture now holds ONE provider client
for the process (address readable and asserted constant), so every timing
below provably went through the same object:
same-client pair (sleep=4000ms): A timeout=700ms -> 703ms, B timeout=2600ms -> 2524ms
OK two calls on ONE client took their OWN timeouts (Δ = 1821ms)
sync timed_call(sleep=6000ms, timeout=800ms) -> 841ms
sync timed_call(sleep=6000ms, no timeout) -> -6072ms (completed)
async (sleep=6000ms, timeout=800ms) -> 761ms
async (sleep=6000ms, timeout=3000ms) -> 3089ms
async (sleep=25000ms, no timeout) -> 19564ms (the protocol's own 20s)
OK sub-millisecond timeout refused rather than rounded into the 20s default
Both new checks were confirmed to fail without the change:
* timeouts accepted and dropped on the floor: the 6s call under 800ms
completes at 6027ms and the protocol log says `timeout: 20000`.
* a LATCHED bound (client-scoped semantics simulated in TimeoutMs::as_abi):
steps 3a and 3b still pass — which is why they were not enough — and the
pair check catches it: "two different timeouts on one client produced the
same elapsed time (A=761ms, B=840ms)".
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
117 lines
2.7 KiB
TOML
117 lines
2.7 KiB
TOML
# This file is automatically @generated by Cargo.
|
|
# It is not intended for manual editing.
|
|
version = 4
|
|
|
|
[[package]]
|
|
name = "itoa"
|
|
version = "1.0.18"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
|
[[package]]
|
|
name = "logos-lidl-gen"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"serde",
|
|
"serde_json",
|
|
"syn",
|
|
]
|
|
|
|
[[package]]
|
|
name = "logos-rust-sdk"
|
|
version = "0.3.0"
|
|
dependencies = [
|
|
"serde",
|
|
"serde_json",
|
|
]
|
|
|
|
[[package]]
|
|
name = "memchr"
|
|
version = "2.8.0"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
|
[[package]]
|
|
name = "proc-macro2"
|
|
version = "1.0.106"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
dependencies = [
|
|
"unicode-ident",
|
|
]
|
|
|
|
[[package]]
|
|
name = "quote"
|
|
version = "1.0.45"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
dependencies = [
|
|
"proc-macro2",
|
|
]
|
|
|
|
[[package]]
|
|
name = "serde"
|
|
version = "1.0.228"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
dependencies = [
|
|
"serde_core",
|
|
"serde_derive",
|
|
]
|
|
|
|
[[package]]
|
|
name = "serde_core"
|
|
version = "1.0.228"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
dependencies = [
|
|
"serde_derive",
|
|
]
|
|
|
|
[[package]]
|
|
name = "serde_derive"
|
|
version = "1.0.228"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
dependencies = [
|
|
"proc-macro2",
|
|
"quote",
|
|
"syn",
|
|
]
|
|
|
|
[[package]]
|
|
name = "serde_json"
|
|
version = "1.0.149"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
dependencies = [
|
|
"itoa",
|
|
"memchr",
|
|
"serde",
|
|
"serde_core",
|
|
"zmij",
|
|
]
|
|
|
|
[[package]]
|
|
name = "syn"
|
|
version = "2.0.117"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
dependencies = [
|
|
"proc-macro2",
|
|
"quote",
|
|
"unicode-ident",
|
|
]
|
|
|
|
[[package]]
|
|
name = "unicode-ident"
|
|
version = "1.0.24"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
|
|
[[package]]
|
|
name = "zmij"
|
|
version = "1.0.21"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|