Skip to content

Replace per-request Proc in Router#transaction with a helper#2692

Open
ericproulx wants to merge 1 commit intomasterfrom
perf/router-transaction-no-proc
Open

Replace per-request Proc in Router#transaction with a helper#2692
ericproulx wants to merge 1 commit intomasterfrom
perf/router-transaction-no-proc

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

Summary

Router#transaction allocated a cascade_or_return_response proc on every request. The proc had to live inside the method because it relied on non-local return to exit transaction when a non-cascading response arrived — the comment on top of the method calls this out explicitly. That proc 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 answers "should this response halt processing?" — returning true for a final (non-cascading) response and false when the response is absent or cascading; closing the cascading body happens inside the helper as a side effect. transaction then uses explicit early-returns for the halt case and plain assignments for the cascade flag. No proc needed; behavior identical.

Perf / Benchmarks

Isolated microbenchmark of the cascade-decision shape, 1,000 iterations:

scenario current variant speedup allocations
cascading response 3.12 M i/s 6.88 M i/s 2.21x 1 → 0 objects
hit response (final) 3.07 M i/s 9.82 M i/s 3.20x 1 → 0 objects
nil response 5.07 M i/s 15.7 M i/s 3.10x 1 → 0 objects

One proc allocation saved per request on the router dispatch path.

Test plan

  • bundle exec rspec — 2,236 examples, 0 failures.
  • bundle exec rubocop lib/grape/router.rb — clean.
  • CI green.

🤖 Generated with Claude Code

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.

## Benchmark (1k iterations, isolated proc shape)

                      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>
@ericproulx ericproulx force-pushed the perf/router-transaction-no-proc branch from 548846f to 396ead4 Compare April 21, 2026 12:21
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

Danger Report

No issues found.

View run

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