Skip to main content

resonate-basic-debugging-rust

Debug and troubleshoot Resonate applications using the Rust SDK. Use when investigating registration errors, serde serialization failures, tokio runtime mismatches, install issues, or SDK-version-specific caveats of the early-development Rust SDK.

跳到安装

来源信息

仓库
resonatehq/resonate-skills
最近来源活动
2026年8月6日 11:59
检测到的 SKILL.md 语言
英语
星标
6
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
resonate-basic-debugging-rust
description
Debug and troubleshoot Resonate applications using the Rust SDK. Use when investigating registration errors, serde serialization failures, tokio runtime mismatches, install issues, or SDK-version-specific caveats of the early-development Rust SDK.
license
Apache-2.0
# Resonate Basic Debugging — Rust > **SDK note.** This SDK is in active development (v0.6.0, published on crates.io), and documented behaviors may shift between releases. Failure modes listed here are what the documented surface produces; new ones will appear as the SDK grows. ## Overview Rust's failure modes differ from TS's and Python's. Compile-time type errors catch many bugs the other SDKs only discover at runtime; the remaining runtime failures are usually serde, tokio, or registration-name related. This skill covers the shapes you'll actually see. For the language-agnostic replay + recovery mental model, read `durable-execution` first. ## Triage flow 1. **Does it compile?** If `cargo build` fails, you're in type-error territory (likely: missing `Result<T>` return, wrong first-parameter type, forgotten `&` on Context/Info, missing `?` on an await) 2. **Does it register?** `resonate.register(fn)` returns a `Result`; unwrap it and look at the error (duplicate name, bad signature) 3. **Does the function kind match expectations?** The SDK infers kind from the first parameter — `&Context` vs `&Info` vs a value type 4. **Is serde happy?** Input/output types need `Serialize` + `Deserialize` derives; stack traces mentioning `serde_json::Error` mean a type doesn't derive correctly 5. **Is the tokio runtime correct?** `#[tokio::main]` or `#[tokio::test(flavor = "multi_thread")]` needed; single-threaded runtime can deadlock on SDK internals 6. **Check server + SDK compatibility.** The SDK version in `Cargo.toml` must align with the server version you're testing against; check the SDK CHANGELOG for compatibility notes ## Install / dependency issues **Symptom:** `cannot find crate 'resonate'` or version-resolution errors. **Cause:** Check your `Cargo.toml`. The SDK is published on crates.io — pin the current release: ```toml [dependencies] resonate = { package = "resonate-sdk", version = "0.6" } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } ``` For pre-release work that must track `main` directly (e.g. to pick up a not-yet-released fix): ```toml resonate = { package = "resonate-sdk", git = "https://github.com/resonatehq/resonate-sdk-rs", branch = "main" } ``` Run `cargo update -p resonate-sdk` to pull the latest version when upstream changes. ## Registration errors | Symptom | Likely cause | Fix | |---|---|---| | `register` returns `Err(AlreadyRegistered)` | Same function registered twice | Register once per process; check for accidentally-looped registration in tests | | `register` returns `Err(BadSignature)` | Function doesn't match one of the 3 valid shapes | First param must be `&Context`, `&Info`, or a value type deriving `Deserialize`. Return must be `Result<T>` where T derives `Serialize` | | Runtime `FunctionNotRegistered` on an RPC call | Caller's name doesn't match registered name | If you used `#[resonate::function(name = "custom")]`, callers must use `"custom"`. Default registered name is the function's Rust identifier | ## Serde errors **Symptom:** `serde_json::Error { ... }` at runtime when a function result crosses a checkpoint or RPC boundary. **Cause:** Input or output type doesn't derive `Serialize` / `Deserialize`. Every argument and return value gets serialized by Resonate. **Fix:** ```rust use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Serialize, Deserialize)] struct Order { id: String, amount: f64, } #[resonate::function] async fn process(ctx: &Context, order: Order) -> Result<Order> { // ... Ok(order) } ``` Common sub-issues: - `#[serde(rename_all = "snake_case")]` if your JSON payload conventions differ from Rust field names - `#[serde(skip_serializing_if = "Option::is_none")]` for optional fields - Enums need `#[serde(tag = "type")]` for internally-tagged representations; pick a representation and stick with it across all workers ## `ctx` vs `info` confusion **Symptom:** "Cannot find method `run` on `&Info`" at compile time. **Cause:** Only `&Context` has `run`, `rpc`, `sleep`. `&Info` gives metadata but not execution capabilities. **Fix:** use `&Context` as the first parameter when the function needs to orchestrate sub-tasks. | You want | Use | |---|---| | Sub-task invocation | `ctx: &Context` | | Metadata only (read execution ID, parent ID) | `info: &Info` | | Stateless pure computation | no Context/Info; just value types | ## Missing `?` on `.await` **Symptom:** compile error about `Result<String>` doesn't implement something, or `future cannot be awaited`. **Cause:** Every async method in the SDK returns a `Future<Output = Result<T>>`. Awaiting gives you `Result<T>`; you still need `?` to extract `T`: ```rust // Bad — `result` is Result<String>, not String let result = ctx.run(leaf, "input".into()).await; // Good let result: String = ctx.run(leaf, "input".into()).await?; ``` ## `.spawn()` is synchronous — don't add `.await` after it **Symptom:** compile error like "no method named `poll` found", a type-mismatch where the compiler expected `DurableFuture<T>` but got an opaque future type, or a lifetime/borrow error that only appears on the `.spawn()` line. **Cause:** As of SDK 0.5.0+, `ctx.run(...).spawn()` / `ctx.rpc(...).spawn()` on a `Context` is **synchronous** — it returns the handle directly (`ctx.run` → `Result<DurableFuture<T>>`, `ctx.rpc` → `Result<RemoteFuture<T>>`), without suspending. Adding `.await` after `.spawn()` is an old habit that no longer compiles. ```rust // BAD — .await after .spawn() fails to compile in 0.5.0+ let fut = ctx.run(leaf, "input".into()).spawn().await?; // CORRECT — .spawn() is sync; unwrap with ? then await the DurableFuture later let fut = ctx.run(leaf, "input".into()).spawn()?; // ... do other work ... // later: a single .await on the DurableFuture gets the actual T let result: String = fut.await?; ``` This differs from TS/Python's `begin_run` / `ctx.rfi`, which return a promise-like handle from an async call. In Rust, you use `?` (not `.await?`) to unwrap the `Result<DurableFuture>` from `.spawn()`, then a single `.await?` later to get `T`. ## tokio runtime mismatches **Symptom:** deadlock at startup, or "Cannot drop runtime in a runtime" panic. **Cause:** wrong tokio runtime flavor, or nested `Runtime::block_on` inside an already-running runtime. **Fix:** use `#[tokio::main]` on your entry point with the default multi-threaded runtime. Do NOT wrap Resonate calls in `Handle::block_on` inside a durable function. ```rust #[tokio::main] async fn main() -> Result<()> { let resonate = Resonate::local(); resonate.register(my_fn).unwrap(); let result: String = resonate.run("id", my_fn, "input".into()).await?; println!("{}", result); resonate.stop().await?; Ok(()) } ``` For tests, prefer `#[tokio::test(flavor = "multi_thread")]` over the default single-thread flavor when tests involve multiple workers. ## Non-determinism regressions Durable functions replay from the last checkpoint. Any non-deterministic code above a checkpoint can cause divergence. Common Rust-specific footguns: ```rust // BAD — system time changes between runs #[resonate::function] async fn bad(ctx: &Context) -> Result<()> { let now = std::time::SystemTime::now(); if now > SOME_THRESHOLD { ctx.run(branch_a, "".into()).await?; } else { ctx.run(branch_b, "".into()).await?; } Ok(()) } // BAD — random values change between runs use rand::Rng; #[resonate::function] async fn bad2(ctx: &Context) -> Result<()> { let roll = rand::thread_rng().gen_range(0..100); // branch on roll — different on each replay Ok(()) } ``` The SDK does NOT expose `ctx.time.time()` / `ctx.random.random()` helpers. Until those land, the safe pattern is: - Do non-deterministic work inside a leaf (so the value is checkpointed) - Or derive branches from the invocation's stable ID / input args, not runtime randomness ```rust #[resonate::function] async fn good(ctx: &Context, input: String) -> Result<()> { // random work inside a checkpointed leaf let roll = ctx.run(roll_dice, ()).await?; if roll > 50 { ctx.run(branch_a, input).await?; } else { ctx.run(branch_b, input).await?; } Ok(()) } #[resonate::function] async fn roll_dice(_: ()) -> Result<u32> { Ok(rand::thread_rng().gen_range(0..100)) } ``` ## Minimal repro ```rust // src/main.rs use resonate::prelude::*; #[tokio::main] async fn main() -> Result<()> { let resonate = Resonate::local(); resonate.register(ping).unwrap(); let result: String = resonate.run("ping:alice", ping, "Alice".into()).await?; println!("{}", result); resonate.stop().await?; Ok(()) } #[resonate::function] async fn ping(name: String) -> Result<String> { Ok(format!("pong {}", name)) } ``` If this fails, the problem is infrastructure (Cargo deps, tokio runtime, SDK version). If it succeeds but your real code fails, diff your function signatures against this template. ## Server compatibility The Rust SDK (v0.6.0) ships with tested compatibility against the Resonate server; check the SDK CHANGELOG for the minimum server version. Before reporting a bug, verify: 1. Your SDK version — `cargo metadata | grep resonate` for the resolved version 2. Your server version — `resonate --version` on the server binary 3. The SDK's compatibility notes — check the SDK's CHANGELOG or README on GitHub Expect that the API surface may shift between 0.x releases until the SDK reaches 1.0. ## CLI one-liners ```shell resonate dev # local dev server resonate tree <invocation-id> # call-graph resonate promises get <id> # single promise state resonate promises search 'order:*' # prefix search resonate promises resolve <id> --data '{}' # settle a pending promise ``` The CLI is SDK-agnostic; same commands work for TS, Python, Rust worker ecosystems. ## Rust SDK API coverage status Cross-reference with `resonate-basic-durable-world-usage-rust` for the full treatment; quick summary for debug triage: ### Exists in v0.6.0 source (even if `rust.mdx` doesn't mention it) - `ctx.promise::<T>()` — Context-side HITL primitive (source: `resonate/src/context.rs:335`) - `ctx.get_dependency::<T>()` + `Info::get_dependency::<T>()` — type-dispatched DI (source: `context.rs:120`, `info.rs:43`) - `ctx.detached(func, args)` — fire-and-forget remote execution (source: `context.rs:405`); `.spawn()?` returns a `DetachedHandle`, and `handle.id().await?` gives the durable promise ID - `ctx.info()` returning extra accessors `branch_id`, `tags` - `resonate.with_dependency::<T>(value)` — ephemeral-side DI builder If a workflow is mysteriously missing one of these, the issue is likely *docs staleness*, not SDK absence. Cite source paths when an agent reviewer questions whether an API exists. ### NOT in v0.6.0 source - `ctx.random.random()` / `ctx.time.time()` — do non-det work inside a leaf so it's checkpointed - `ctx.panic()` / `ctx.assert()` — use Rust's `panic!` / `assert!` (non-recoverable) or `Result` propagation (recoverable) Each of these may land in a future version; check `docs/develop/rust.mdx` AND the `resonate-sdk-rs` source when a new release ships — iter-18/19 review showed docs can lag source meaningfully. ## Related skills - `resonate-basic-ephemeral-world-usage-rust` — Client APIs at the process-entry layer - `resonate-basic-durable-world-usage-rust` — Context APIs inside durable functions - `durable-execution` + `resonate-philosophy` — foundational; many debug sessions end up being about patterns warned against here - `resonate-basic-debugging-typescript` + `-python` — sibling SDKs for comparison
在 GitHub 查看