Skip to content

Build the cookie jar only on use and drop its deferral Proc#2757

Open
ericproulx wants to merge 1 commit into
masterfrom
perf/lazy-response-cookies
Open

Build the cookie jar only on use and drop its deferral Proc#2757
ericproulx wants to merge 1 commit into
masterfrom
perf/lazy-response-cookies

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

@ericproulx ericproulx commented Jun 1, 2026

Makes the response-cookie path lazy at the request boundary, in two related steps.

1. Build the jar only when a cookie is touched

Grape::Endpoint#build_response_cookies runs on every request and delegated response_cookiescookiesrequest.cookies, materializing the Grape::Cookies jar even when the handler never touched a cookie (the jar's response_cookies then immediately no-ops). Gate it on a new Grape::Request#cookies? predicate, true only once the jar exists:

def build_response_cookies
  return unless request.cookies?
  ...
end

A Set-Cookie is only ever emitted after a cookies[...] = write, which always materializes the jar first — so cookies? is true exactly when there could be something to flush. Behaviour is unchanged; a cookie-free request now allocates no jar.

2. Drop the jar's deferral Proc

The -> { rack_cookies } Proc existed (since #2549) to defer the request-cookie parse, because the jar used to be built on every request. Now that step 1 only builds it when a cookie is actually read or written — and any access forces the parse immediately (a write via []= always did) — the Proc no longer earns its keep. Parse rack_cookies eagerly in the constructor and replace the is_a?(Proc) lazy reader with a plain attr_reader, dropping the closure allocation and a per-access branch.

The ActiveSupport::HashWithIndifferentAccess wrapping is unchanged — it backs Grape's string/symbol-indifferent cookie access (set_cookie username=… is read back as cookies[:username]).

Result

A request that never touches a cookie now allocates zero Grape::Cookies/Proc objects (was ~10 on the flush path); a cookie-using request loses the extra Proc closure and the per-access is_a?(Proc) check.

Tests

spec/grape/endpoint_spec.rb #cookies — 5 examples, 0 failures (set / update / delete / symbol-keyed read / "does not set response cookies"). Rubocop clean.

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Danger Report

No issues found.

View run

@ericproulx ericproulx force-pushed the perf/lazy-response-cookies branch from a60c995 to 8075e9b Compare June 1, 2026 21:58
Two related steps that make the response-cookie path lazy at the request
boundary:

- `build_response_cookies` runs on every request and went through
  `cookies` -> `request.cookies`, materializing the `Grape::Cookies` jar
  even when the handler never touched a cookie. Gate it on a new
  `Grape::Request#cookies?` predicate (true only once the jar exists),
  so a cookie-free request allocates no jar at all.

- With the jar now built only when a cookie is actually read or written,
  its `-> { rack_cookies }` deferral Proc no longer earns its keep: any
  access immediately forces the parse, and a write (`[]=`) always did.
  Parse `rack_cookies` eagerly in the constructor and replace the
  `is_a?(Proc)` lazy reader with a plain `attr_reader`, dropping the
  closure allocation and the per-access branch.

The `ActiveSupport::HashWithIndifferentAccess` wrapping is unchanged --
it backs Grape's string/symbol-indifferent cookie access.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ericproulx ericproulx force-pushed the perf/lazy-response-cookies branch from 8075e9b to c2b0cf5 Compare June 1, 2026 22:11
@ericproulx ericproulx changed the title Skip response-cookie flush when no cookie was touched Build the cookie jar only on use and drop its deferral Proc Jun 1, 2026
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.

2 participants