From 5d6b4f7fec33bd5caf3b0aedd6d356f17f5a251e Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Fri, 11 Sep 2026 10:37:23 +0200 Subject: [PATCH] Stop building a backtrace in the default rescue handler `Middleware::Error#default_rescue_handler`, which renders any exception matched by `rescue_from :all` or `rescue_from SomeError` given without a block, passed `backtrace: exception.backtrace` to the error payload. `Exception#backtrace` builds the whole backtrace as Strings, which at a request's stack depth is the dearest part of rendering the error, and the result was then discarded unless the API had asked for `rescue_from ..., backtrace: true`. `#resolved_backtrace` already falls back to `original_exception.backtrace` when a backtrace is wanted, and the handler passes `original_exception`, so the eager read goes. A 500 from `rescue_from :all` went from 31.5k to 60.8k requests per second. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 1 + lib/grape/middleware/error.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc7d3a3e..26c09ee01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2918](https://github.com/ruby-grape/grape/pull/2918): Skip the dry-types round trip when a value already is the declared type - [@ericproulx](https://github.com/ericproulx). * [#2917](https://github.com/ruby-grape/grape/pull/2917): Read path captures out of the router's union match instead of re-running the route's pattern - [@ericproulx](https://github.com/ericproulx). * [#2921](https://github.com/ruby-grape/grape/pull/2921): Pin the router's request-time isolation regressions through requests instead of its instance variables - [@ericproulx](https://github.com/ericproulx). +* [#2931](https://github.com/ruby-grape/grape/pull/2931): Stop building a backtrace in the default `rescue_from` handler unless the API asked for one - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 4.0.0 (2026-09-07) diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index a2dd7c48a..7e5b0bd53 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -192,11 +192,13 @@ def failsafe_payload(headers) ) end + # No +backtrace:+: #resolved_backtrace reads it off +original_exception+ + # when the API asked for one, and only then, since building it is the + # dearest part of rendering the error. def default_rescue_handler(exception) error_response( Grape::Exceptions::ErrorResponse.new( message: exception.message, - backtrace: exception.backtrace, original_exception: exception ) )