Skip to content

Commit cb50d5f

Browse files
ericproulxclaude
andcommitted
Replace per-request Proc in Router#transaction with a helper method
The `cascade_or_return_response` proc inside `Router#transaction` was allocated on every request because it relied on non-local `return` to exit the enclosing method when a non-cascading response arrived. That behavior was doing two things at once — closing the cascading body as a side effect, and halting transaction processing. Split them: a plain `halt?(response)` helper returns `true` when the response is final (and should be returned as-is), `false` when it's either absent or cascading; closing the body lives inside the helper as a side effect. The `transaction` method then uses explicit early-returns for the halt case and plain assignments for the cascade flag. current variant speedup alloc cascading response 3.12 M/s 6.88 M/s 2.21x 1 → 0 objects hit response (final) 3.07 M/s 9.82 M/s 3.20x 1 → 0 objects nil response 5.07 M/s 15.7 M/s 3.10x 1 → 0 objects Saves one proc allocation per request in the router's `transaction` dispatch — plus 2–3x faster control flow in the cascade-decision step. No behavior change; all 2,236 specs pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f2ef861 commit cb50d5f

2 files changed

Lines changed: 21 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* [#2689](https://github.com/ruby-grape/grape/pull/2689): Avoid empty-hash merges on request hot paths - [@ericproulx](https://github.com/ericproulx).
1414
* [#2690](https://github.com/ruby-grape/grape/pull/2690): Avoid allocating an empty array on every `StackableValues#[]` miss - [@ericproulx](https://github.com/ericproulx).
1515
* [#2691](https://github.com/ruby-grape/grape/pull/2691): Precompute the prefix list in `Middleware::Versioner::Path` - [@ericproulx](https://github.com/ericproulx).
16+
* [#2692](https://github.com/ruby-grape/grape/pull/2692): Replace per-request `Proc` allocation in `Router#transaction` with a `halt?` helper - [@ericproulx](https://github.com/ericproulx).
1617
* Your contribution here.
1718

1819
#### Fixes

lib/grape/router.rb

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,10 @@ def rotation(input, method, env, exact_route)
103103
end
104104

105105
def transaction(input, method, env)
106-
# using a Proc is important since `return` will exit the enclosing function
107-
cascade_or_return_response = proc do |response|
108-
if response
109-
cascade?(response).tap do |cascade|
110-
return response unless cascade
111-
112-
# we need to close the body if possible before dismissing
113-
response[2].close if response[2].respond_to?(:close)
114-
end
115-
end
116-
end
117-
118106
response = yield
119-
last_response_cascade = cascade_or_return_response.call(response)
107+
return response if halt?(response)
108+
109+
last_response_cascade = !response.nil?
120110
last_neighbor_route = greedy_match?(input)
121111

122112
# If last_neighbor_route exists and request method is OPTIONS,
@@ -127,13 +117,29 @@ def transaction(input, method, env)
127117

128118
return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route
129119

130-
last_response_cascade = cascade_or_return_response.call(process_route(route, input, env)) if route
120+
if route
121+
route_response = process_route(route, input, env)
122+
return route_response if halt?(route_response)
123+
124+
last_response_cascade = !route_response.nil?
125+
end
131126

132127
return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route
133128

134129
nil
135130
end
136131

132+
# Returns true if `response` should be returned as-is from the enclosing
133+
# transaction. Closes the body as a side effect when the response is
134+
# cascading so callers can safely try the next match.
135+
def halt?(response)
136+
return false unless response
137+
138+
cascade = cascade?(response)
139+
response[2].close if cascade && response[2].respond_to?(:close)
140+
!cascade
141+
end
142+
137143
def process_route(route, input, env, include_allow_header: false)
138144
args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }
139145
route_params = route.params(input)

0 commit comments

Comments
 (0)