Skip to content

fix(rpc,openapi): prevent lazy router races under concurrent requests - #1734

Merged
dinwwwh merged 1 commit into
1.xfrom
claude/lazy-router-concurrent-requests-df4ca2
Jul 27, 2026
Merged

fix(rpc,openapi): prevent lazy router races under concurrent requests#1734
dinwwwh merged 1 commit into
1.xfrom
claude/lazy-router-concurrent-requests-df4ca2

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Jul 27, 2026

Copy link
Copy Markdown
Member

Problem

When multiple requests hit the server at the same time while a lazy router was still loading, the matchers (StandardRPCMatcher and StandardOpenAPIMatcher) could:

  • Invoke the same lazy loader multiple times, once per concurrent request.
  • Lose routes entirely when a lazy router contained another lazy router — the nested route could then 404 permanently, even for later requests.

Fix

Concurrent requests now share a single load per lazy router: each loader runs exactly once, requests arriving mid-load wait for it instead of reloading or missing the route, and nested lazy routers always resolve correctly. If a loader fails, the request rejects and the next request retries as before.

Tests

New tests in both matchers cover concurrent requests to lazy routers, lazy routers nested inside lazy routers (up to three levels), multiple lazy branches matched in parallel, and retry after loader failure. All of them fail on the previous implementation. Full server and openapi suites pass (808 tests).

Concurrent match() calls iterated the same pendingRouters array with awaits
inside the loop, then reassigned this.pendingRouters wholesale. This caused
duplicate loader invocations and, with a lazy router nested inside another
lazy router, could drop pending routers entirely (permanent 404).

Resolve pending routers one at a time via a memoized init promise shared by
concurrent requests, and remove each entry in place only after init completes.
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
orpc Ready Ready Preview, Comment Jul 27, 2026 10:04am

@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1734

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1734

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1734

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1734

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1734

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1734

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1734

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1734

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1734

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1734

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1734

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1734

@orpc/experimental-pino

npm i https://pkg.pr.new/@orpc/experimental-pino@1734

@orpc/experimental-publisher

npm i https://pkg.pr.new/@orpc/experimental-publisher@1734

@orpc/experimental-publisher-durable-object

npm i https://pkg.pr.new/@orpc/experimental-publisher-durable-object@1734

@orpc/experimental-ratelimit

npm i https://pkg.pr.new/@orpc/experimental-ratelimit@1734

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1734

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1734

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1734

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1734

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1734

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1734

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1734

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1734

@orpc/standard-server-fastify

npm i https://pkg.pr.new/@orpc/standard-server-fastify@1734

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1734

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1734

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1734

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1734

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1734

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1734

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1734

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1734

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1734

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1734

commit: d78d4b5

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@dinwwwh dinwwwh changed the title fix(server,openapi): prevent lazy router races under concurrent requests fix(rpc,openapi): prevent lazy router races under concurrent requests Jul 27, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — fixes a race condition in lazy router resolution under concurrent requests by replacing the for...of + wholesale array reassignment with a one-at-a-time resolution + promise-memoization pattern.

  • Replace for...of + array rebuild with while + find + await-one — resolves pending routers one at a time and re-scans after each, so nested lazy routers pushed during init() are picked up by the same request instead of being dropped.
  • Memoize init via initPromise on the pending entry — concurrent requests share a single unlazy() + init() invocation via ??=; duplicate loader calls and double route registrations are eliminated.
  • Splice in place after init completes — pending routers are removed from the array only after resolution, so arriving requests wait on the shared promise rather than missing the route.
  • Clear initPromise on failure for retry — sets pendingRouter.initPromise = undefined in the .catch() handler, preserving the previous retry-on-next-request behavior.
  • Add readonly to pendingRouters — prevents the old wholesale-reassignment pattern at the type level.
  • Add 3 new concurrency + retry tests to each matcher — covers nested lazy + concurrent requests, multi-branch concurrent matching with 3 nesting levels, and retry after loader failure. All fail against the old implementation and pass with the fix.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit ec6d86d into 1.x Jul 27, 2026
6 checks passed
@dinwwwh
dinwwwh deleted the claude/lazy-router-concurrent-requests-df4ca2 branch July 30, 2026 02:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant