fix(generator): emit async fn for service-method handlers — closes #137 #138

Merged
timur merged 2 commits from issue-137-svc-handler-async into development 2026-05-25 09:52:43 +00:00
Owner

Closes #137.

What this PR does

Closes the codegen-asymmetry gap left by hero_rpc#133. That PR converted the CRUD handler emitter to async fn and dropped spawn_blocking around CRUD dispatch. It did NOT touch the service-method handler emitter — that still produced sync fn. The result, exposed by hero_rpc#134 (generated E2E tests for service-block methods): anyone whose service-method body needed to call async code had to re-introduce a block_in_place(|| Handle::current().block_on(...)) shim — exactly the pattern #133 was supposed to eliminate.

The hero_service template carried that workaround in catalog/rpc.rs (#11 there); the matching hero_service PR (filed alongside this one) drops it.

Before / after

RPC server (async)
    ↓ direct .await                    ← #133 cleaned this for CRUD
OSIS CRUD handlers (async fn)          ← #133
OSIS service-method handlers:
   before: SYNC fn   → block_in_place + block_on bridge inside body
   after:  async fn  → direct .await                          ← THIS PR

Zero sync↔async crossings now in any generated handler in any (CRUD or service) path.

Changes

Commit 27ea6ae — generator: emit async fn for service-method handlers

  • crates/generator/src/rust/rust_rpc.rs::generate_trait_method — service-trait method signatures now async fn, matching the SDK-side #[method(...)] async fn from build/emit/rust_rpc2.rs::render_service_methods.

Commit c57b90b — osis: drop spawn_blocking around service-method dispatch

  • crates/osis/src/rpc/server.rs — drop spawn_blocking wrappers at the service-method dispatch sites (~line 1440, ~line 1915 by old numbering). Direct .await now. The retained spawn_blocking calls at lines ~905, ~992, ~1401, ~1869 are around the legacy sync OsisRpcHandler::handle_request (seed / bootstrap path) — not in scope for this issue.
  • crates/osis/src/rpc/dispatch.rs, handler.rs — doc-comment updates reflecting the new contract.
  • examples/recipe_example/inject/crates/hero_recipes_server/src/recipes/rpc.rs + tests_error_category.rs — regenerated handler is async; the matching hand-written test fixture drops its block_on bridge.

Validation

  • cargo build --workspace clean.
  • cargo test -p hero_rpc_osis --features rpc --lib85/85 pass.
  • cargo test -p hero_rpc_generator --lib145/145 pass.
  • grep -rn "spawn_blocking" crates/osis/src/rpc/ — remaining sites are the legacy sync OsisRpcHandler bridge in the seed/bootstrap path (intentional, not in scope for this issue).
  • Closes #137
  • Motivating PRs: #133 (CRUD half), #134 (surfaced the gap)
  • Closeout of meta hero_skills#262 progresses; prereq for #132 (legacy-service migration).
Closes #137. ## What this PR does Closes the codegen-asymmetry gap left by hero_rpc#133. That PR converted the CRUD handler emitter to `async fn` and dropped `spawn_blocking` around CRUD dispatch. It did NOT touch the service-method handler emitter — that still produced sync `fn`. The result, exposed by hero_rpc#134 (generated E2E tests for service-block methods): anyone whose service-method body needed to call async code had to re-introduce a `block_in_place(|| Handle::current().block_on(...))` shim — exactly the pattern #133 was supposed to eliminate. The hero_service template carried that workaround in `catalog/rpc.rs` (#11 there); the matching hero_service PR (filed alongside this one) drops it. ## Before / after ``` RPC server (async) ↓ direct .await ← #133 cleaned this for CRUD OSIS CRUD handlers (async fn) ← #133 OSIS service-method handlers: before: SYNC fn → block_in_place + block_on bridge inside body after: async fn → direct .await ← THIS PR ``` Zero sync↔async crossings now in any generated handler in any (CRUD or service) path. ## Changes **Commit `27ea6ae`** — generator: emit `async fn` for service-method handlers - `crates/generator/src/rust/rust_rpc.rs::generate_trait_method` — service-trait method signatures now `async fn`, matching the SDK-side `#[method(...)] async fn` from `build/emit/rust_rpc2.rs::render_service_methods`. **Commit `c57b90b`** — osis: drop `spawn_blocking` around service-method dispatch - `crates/osis/src/rpc/server.rs` — drop `spawn_blocking` wrappers at the service-method dispatch sites (~line 1440, ~line 1915 by old numbering). Direct `.await` now. The retained `spawn_blocking` calls at lines ~905, ~992, ~1401, ~1869 are around the **legacy sync** `OsisRpcHandler::handle_request` (seed / bootstrap path) — not in scope for this issue. - `crates/osis/src/rpc/dispatch.rs`, `handler.rs` — doc-comment updates reflecting the new contract. - `examples/recipe_example/inject/crates/hero_recipes_server/src/recipes/rpc.rs` + `tests_error_category.rs` — regenerated handler is async; the matching hand-written test fixture drops its block_on bridge. ## Validation - `cargo build --workspace` clean. - `cargo test -p hero_rpc_osis --features rpc --lib` — **85/85 pass**. - `cargo test -p hero_rpc_generator --lib` — **145/145 pass**. - `grep -rn "spawn_blocking" crates/osis/src/rpc/` — remaining sites are the legacy sync `OsisRpcHandler` bridge in the seed/bootstrap path (intentional, not in scope for this issue). ## Links - Closes #137 - Motivating PRs: #133 (CRUD half), #134 (surfaced the gap) - Closeout of meta hero_skills#262 progresses; prereq for #132 (legacy-service migration).
Close the codegen gap left by hero_rpc#133 — the CRUD-handler emitter
gained `async fn` there, but the service-method handler emitter still
produced sync `fn`. Anyone whose service-method body needed to call
async code (the indexer, a cross-domain SDK client, an HTTP/AI client)
had to re-introduce a sync↔async bridge — exactly the
`block_in_place(|| Handle::current().block_on(...))` shim that
hero_rpc#134 had to paste into hero_service's `catalog/rpc.rs` after
regenerating against the new service-method E2E tests.

Changes to `crates/generator/src/rust/rust_rpc.rs`:

- `generate_trait_method` — service-trait method signatures now emit
  `async fn` (matches the SDK-side `#[method(...)] async fn` already
  emitted by `build/emit/rust_rpc2.rs::render_service_methods`).
- `generate_method_impl` (auto-CRUD impls) and `generate_method` (todo!
  stubs) follow the same async-fn switch, so the scaffolded
  contributor file lines up with the trait.
- `generate_trait_impl` (the standalone todo! stub generator) also
  emits `async fn`.

Changes to `crates/generator/src/rust/rust_osis.rs`:

- `generate_service_rpc_methods` — the per-method `_rpc_*` helper that
  unwraps params, takes the handler lock, and dispatches into the
  user-implemented service trait is now `async fn`, and it `.await`s
  the trait call. To keep the dispatcher future `Send` across the
  await, the helper clones an `Arc<Handler>` and drops the
  `std::sync::RwLockReadGuard` *before* awaiting — holding a sync
  guard across an `.await` would make the future non-`Send` and
  block the executor. The service-handler field type changes
  accordingly from `RwLock<Option<Handler>>` to
  `RwLock<Option<Arc<Handler>>>`, and `init_handlers` wraps the
  constructed handler in `Arc::new`.
- The dispatch arm in `handle_rpc` for service methods now `.await`s
  the helper — chaining the all-async `handle_rpc → svc_rpc → user
  trait` path with zero sync↔async bridges.

Net result: handler bodies generated for `service Foo { … }` blocks
are async at the trait surface, so users can `self.domain.x_new(...)
.await`, hit the indexer, or call another service's SDK directly
without re-introducing a blocking shim. Closes the gap that #133
opened and #134 papered over.

Refs hero_rpc#133, hero_rpc#134, hero_rpc#137
osis: drop spawn_blocking around service-method dispatch (hero_rpc#137)
Some checks failed
Test / test (pull_request) Failing after 2m9s
Test / test (push) Failing after 2m58s
c57b90bdae
Follow-on to the generator change in 27ea6ae — now that generated
service-method handlers are async fn (via RPITIT), the dispatch sites
in crates/osis/src/rpc/server.rs (around lines 1440 and 1915) drop
their spawn_blocking wrappers and call the handler directly with .await.

dispatch.rs + handler.rs: update doc comments to reflect the new
contract. The legacy sync OsisRpcHandler::handle_request bridges
(spawn_blocking around lines 905/992/1401/1869) are intentionally
retained for the seed/bootstrap path — those still go through the
legacy sync trait and are not part of this issue's scope.

examples/recipe_example/inject: regenerate the service-method handler
to use async fn + drop the matching block_on bridge from the
hand-written test fixture.
timur merged commit 5d0abe76c6 into development 2026-05-25 09:52:43 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_rpc!138
No description provided.