Skip to content

Commit 4503760

Browse files
committed
perf(router): reuse empty middleware state
1 parent caee53a commit 4503760

1 file changed

Lines changed: 40 additions & 22 deletions

File tree

storage/framework/core/router/src/stacks-router.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,20 @@ const routeActionRegistry = new Map<string, RouterAction>()
604604
const CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])
605605
const CSRF_SEEDED_BY_HANDLE_REQUEST = Symbol.for('stacks.router.csrfSeededByHandleRequest')
606606

607+
interface ResolvedMiddleware {
608+
name: string
609+
handler: MiddlewareHandler
610+
priority: number
611+
}
612+
613+
interface MiddlewareTiming {
614+
name: string
615+
ms: number
616+
}
617+
618+
const EMPTY_RESOLVED_MIDDLEWARE: ResolvedMiddleware[] = []
619+
const EMPTY_MIDDLEWARE_TIMINGS: MiddlewareTiming[] = []
620+
607621
/**
608622
* Named route registry — keeps the original path plus the precomputed
609623
* placeholder names and a per-param replacement regex so `url()` can
@@ -1671,12 +1685,9 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Rout
16711685
// contract requiring CORS to precede auth/throttle so 4xx
16721686
// responses still carry the right headers. See
16731687
// stacksjs/stacks#1863, #1859 (H-1).
1674-
interface ResolvedMiddleware {
1675-
name: string
1676-
handler: MiddlewareHandler
1677-
priority: number
1678-
}
1679-
const resolved: ResolvedMiddleware[] = []
1688+
const resolved: ResolvedMiddleware[] = middlewareEntries.length === 0
1689+
? EMPTY_RESOLVED_MIDDLEWARE
1690+
: []
16801691
for (const middlewareEntry of middlewareEntries) {
16811692
const parsed = await parseMiddlewareEntry(middlewareEntry)
16821693
const { name: middlewareName, params } = parsed
@@ -1725,10 +1736,13 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Rout
17251736
// Stable sort — V8 + Bun guarantee Array.sort is stable since 2018,
17261737
// so same-priority entries preserve insertion order. This keeps
17271738
// declared sequencing within a priority band predictable.
1728-
resolved.sort((a, b) => a.priority - b.priority)
1739+
if (resolved.length > 1)
1740+
resolved.sort((a, b) => a.priority - b.priority)
17291741

17301742
// Run middleware in priority order
1731-
const middlewareTimings: Array<{ name: string, ms: number }> = []
1743+
const middlewareTimings: MiddlewareTiming[] = resolved.length === 0
1744+
? EMPTY_MIDDLEWARE_TIMINGS
1745+
: []
17321746

17331747
/*
17341748
* One budget for the chain, armed at most once per request.
@@ -1749,19 +1763,22 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Rout
17491763
let chainTimer: ReturnType<typeof setTimeout> | undefined
17501764
let chainBudget: Promise<never> | undefined
17511765
let runningMiddleware = ''
1752-
const armChainBudget = (): Promise<never> => {
1753-
if (!chainBudget) {
1754-
chainBudget = new Promise<never>((_, reject) => {
1755-
chainTimer = setTimeout(
1756-
() => reject(new Error(`Middleware '${runningMiddleware}' exceeded ${MIDDLEWARE_TIMEOUT_MS}ms`)),
1757-
MIDDLEWARE_TIMEOUT_MS,
1758-
)
1759-
})
1760-
// Marks the budget handled so a chain that finishes normally does not
1761-
// leave an unhandled rejection behind when the timer eventually fires.
1762-
chainBudget.catch(() => {})
1766+
let armChainBudget: (() => Promise<never>) | undefined
1767+
if (resolved.length > 0) {
1768+
armChainBudget = (): Promise<never> => {
1769+
if (!chainBudget) {
1770+
chainBudget = new Promise<never>((_, reject) => {
1771+
chainTimer = setTimeout(
1772+
() => reject(new Error(`Middleware '${runningMiddleware}' exceeded ${MIDDLEWARE_TIMEOUT_MS}ms`)),
1773+
MIDDLEWARE_TIMEOUT_MS,
1774+
)
1775+
})
1776+
// Marks the budget handled so a chain that finishes normally does not
1777+
// leave an unhandled rejection behind when the timer eventually fires.
1778+
chainBudget.catch(() => {})
1779+
}
1780+
return chainBudget
17631781
}
1764-
return chainBudget
17651782
}
17661783

17671784
try {
@@ -1776,7 +1793,7 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Rout
17761793
const outcome = middleware.handle(enhancedReq)
17771794
// Nothing to time out when the layer already finished.
17781795
if (outcome && typeof (outcome as Promise<void>).then === 'function')
1779-
await Promise.race([outcome as Promise<void>, armChainBudget()])
1796+
await Promise.race([outcome as Promise<void>, armChainBudget!()])
17801797
const elapsedMs = Number(process.hrtime.bigint() - mwStart) / 1_000_000
17811798
middlewareTimings.push({ name: middlewareName, ms: elapsedMs })
17821799
}
@@ -1846,7 +1863,8 @@ function createMiddlewareHandler(routeKey: string, handler: StacksHandler): Rout
18461863
finally {
18471864
// One timer, cleared on every way out of the chain - including the
18481865
// early returns above - so a settled request leaves nothing pending.
1849-
clearTimeout(chainTimer)
1866+
if (chainTimer)
1867+
clearTimeout(chainTimer)
18501868
}
18511869

18521870
// Call the actual handler with the enhanced request.

0 commit comments

Comments
 (0)