Extend hero_rpc#133's async conversion to service-method handlers — close the codegen gap #137

Closed
opened 2026-05-25 09:32:45 +00:00 by timur · 1 comment
Owner

Goal

Extend hero_rpc#133's async conversion to service-method handlers. #133 converted the CRUD handler emitter to async fn and removed spawn_blocking around CRUD dispatch. It did not touch the service-method handler emitter — that one still emits sync fn. The result: any service-method handler that needs to call async code (the indexer, another service, an HTTP API) must re-introduce a block_in_place + block_on bridge inside the handler body.

This was exposed by hero_rpc#134 (which closed #131 and added generated E2E tests for service block methods) — the regenerated catalog/rpc.rs in hero_service had to add tokio::task::block_in_place(|| Handle::current().block_on(...)) to call async code from a sync service-method handler. That's exactly the pattern #133 was supposed to eliminate.

What's broken today

RPC server (async)
    ↓ direct .await   ← #133 fixed this for CRUD
OSIS CRUD handlers (async fn)   ← #133's work
OSIS service-method handlers (SYNC fn)   ← ❌ still sync
    ↓ if it needs to call async code, sync→async bridge
    ↓ block_in_place + Handle::block_on(...)
async dep (indexer, etc.)

What we want

RPC server (async)
    ↓ direct .await
OSIS CRUD handlers (async fn)              ← #133
OSIS service-method handlers (async fn)   ← THIS ISSUE
    ↓ direct .await
async dep

Zero sync↔async bridges anywhere inside any generated handler.

What to change

  1. Generator — wherever the service-method handler signature is emitted (CustomMethodHandler / service-method handler scaffold in crates/generator/src/rust/rust_osis.rs or wherever else service handler stubs are produced) emit async fn instead of fn. Return types should be wrapped to indicate impl Future + Send via RPITIT, matching the pattern already used by OsisAppRpcHandler (the CRUD handler trait).
  2. OSIS RPC dispatch — wherever service-method handlers are invoked from the server (crates/osis/src/rpc/server.rs — there are still spawn_blocking wrappers around service-method dispatch at lines ~1443, ~1920 from the #133 work which intentionally kept them for sync user handlers), switch to direct .await once the trait surface is async.
  3. Existing service-method handler impls in hero_service and hero_rpc examples — convert their bodies to async fn and drop any block_in_place workarounds.

Prerequisites

None. Stacks on top of #133 (merged).

Acceptance criteria

  • grep -rn "block_in_place\|block_on" hero_service/crates/<svc>_server/src/ returns no occurrences inside generated or hand-written handler bodies (legitimate uses elsewhere, e.g. in test fixtures, are fine).
  • grep -rn "spawn_blocking" hero_rpc/crates/osis/src/rpc/ returns no occurrences around service-method dispatch (CRUD dispatch was already cleaned in #133).
  • cargo test -p hero_rpc_osis clean.
  • lab service hero_service --test runs all five layers green.
  • The 14 generated service-method E2E tests added in #134 still pass.

Out of scope

  • Migrating legacy services to oschema codegen (hero_rpc#132) — orthogonal. This issue is purely about closing the architectural gap left by #133.
  • The block_in_place workaround in hero_service's catalog/rpc.rs from #134 — it gets removed automatically once this issue lands and hero_service regenerates.
## Goal Extend [hero_rpc#133](https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/133)'s async conversion to **service-method handlers**. #133 converted the CRUD handler emitter to `async fn` and removed `spawn_blocking` around CRUD dispatch. It did not touch the service-method handler emitter — that one still emits sync `fn`. The result: any service-method handler that needs to call async code (the indexer, another service, an HTTP API) must re-introduce a `block_in_place` + `block_on` bridge inside the handler body. This was exposed by [hero_rpc#134](https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/134) (which closed #131 and added generated E2E tests for `service` block methods) — the regenerated `catalog/rpc.rs` in hero_service had to add `tokio::task::block_in_place(|| Handle::current().block_on(...))` to call async code from a sync service-method handler. That's exactly the pattern #133 was supposed to eliminate. ## What's broken today ``` RPC server (async) ↓ direct .await ← #133 fixed this for CRUD OSIS CRUD handlers (async fn) ← #133's work OSIS service-method handlers (SYNC fn) ← ❌ still sync ↓ if it needs to call async code, sync→async bridge ↓ block_in_place + Handle::block_on(...) async dep (indexer, etc.) ``` ## What we want ``` RPC server (async) ↓ direct .await OSIS CRUD handlers (async fn) ← #133 OSIS service-method handlers (async fn) ← THIS ISSUE ↓ direct .await async dep ``` Zero sync↔async bridges anywhere inside any generated handler. ## What to change 1. **Generator** — wherever the service-method handler signature is emitted (CustomMethodHandler / service-method handler scaffold in `crates/generator/src/rust/rust_osis.rs` or wherever else service handler stubs are produced) emit `async fn` instead of `fn`. Return types should be wrapped to indicate `impl Future + Send` via RPITIT, matching the pattern already used by `OsisAppRpcHandler` (the CRUD handler trait). 2. **OSIS RPC dispatch** — wherever service-method handlers are invoked from the server (`crates/osis/src/rpc/server.rs` — there are still `spawn_blocking` wrappers around service-method dispatch at lines ~1443, ~1920 from the #133 work which intentionally kept them for sync user handlers), switch to direct `.await` once the trait surface is async. 3. **Existing service-method handler impls** in hero_service and hero_rpc examples — convert their bodies to `async fn` and drop any `block_in_place` workarounds. ## Prerequisites None. Stacks on top of #133 (merged). ## Acceptance criteria - `grep -rn "block_in_place\|block_on" hero_service/crates/<svc>_server/src/` returns no occurrences inside generated or hand-written handler bodies (legitimate uses elsewhere, e.g. in test fixtures, are fine). - `grep -rn "spawn_blocking" hero_rpc/crates/osis/src/rpc/` returns no occurrences around service-method dispatch (CRUD dispatch was already cleaned in #133). - `cargo test -p hero_rpc_osis` clean. - `lab service hero_service --test` runs all five layers green. - The 14 generated service-method E2E tests added in #134 still pass. ## Out of scope - Migrating legacy services to oschema codegen ([hero_rpc#132](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/132)) — orthogonal. This issue is purely about closing the architectural gap left by #133. - The `block_in_place` workaround in hero_service's `catalog/rpc.rs` from #134 — it gets removed automatically once this issue lands and hero_service regenerates. ## Links - Motivating PR: [hero_rpc#133](https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/133) (closed the CRUD half) - Motivating PR: [hero_rpc#134](https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/134) (surfaced the gap by adding service-method E2E tests) - Meta: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262) - Closeout meta: [hero_rpc#132](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/132) (legacy-service migration — benefits from this landing)
timur closed this issue 2026-05-25 09:52:43 +00:00
Author
Owner

Closeout — both PRs merged.

  • hero_rpc#1385d0abe7 — generator emits async fn for service-method handlers; OSIS dispatch drops spawn_blocking around service-method calls.
  • hero_service#12 → just merged — drops the bridge_sync helper + block_in_place workaround #11 had to add; catalog + greeter handler impls converted to async fn. Zero sync↔async crossings in any handler body.

Architectural state of meta hero_skills#262: both halves of the OSIS async conversion (CRUD via #133, service-method via #137) are landed. The codegen story is consistent — generated handler stubs are async by default.

Runtime validation via lab service hero_service --test is pending an orthogonal lab to hero_service ephemeral-launch interplay issue (installed binary fails silently when spawned by lab --ephemeral). Filed as a separate follow-up; not in scope for this issue.

Next: hero_rpc#132's first sub-issue (hero_indexer migration to oschema codegen) is now unblocked.

Closeout — both PRs merged. - **hero_rpc#138** → `5d0abe7` — generator emits `async fn` for service-method handlers; OSIS dispatch drops `spawn_blocking` around service-method calls. - **hero_service#12** → just merged — drops the `bridge_sync` helper + `block_in_place` workaround #11 had to add; catalog + greeter handler impls converted to `async fn`. Zero sync↔async crossings in any handler body. Architectural state of meta hero_skills#262: both halves of the OSIS async conversion (CRUD via #133, service-method via #137) are landed. The codegen story is consistent — generated handler stubs are async by default. Runtime validation via `lab service hero_service --test` is pending an orthogonal `lab` to `hero_service` ephemeral-launch interplay issue (installed binary fails silently when spawned by `lab --ephemeral`). Filed as a separate follow-up; not in scope for this issue. Next: hero_rpc#132's first sub-issue (hero_indexer migration to oschema codegen) is now unblocked.
Sign in to join this conversation.
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#137
No description provided.