Skip to content

Fix thread safety: move mutable ParamsScope state into per-request ParamScopeTracker#2660

Merged
dblock merged 1 commit intomasterfrom
fix_param_scope_thread_safety
Feb 28, 2026
Merged

Fix thread safety: move mutable ParamsScope state into per-request ParamScopeTracker#2660
dblock merged 1 commit intomasterfrom
fix_param_scope_thread_safety

Conversation

@ericproulx
Copy link
Contributor

Summary

ParamsScope instances are shared across requests (they're definition-time objects). Two mutable ivars were being written during request processing, creating a race condition under concurrent requests:

  • @index — set by AttributesIterator while iterating array params
  • @params_meeting_dependency — set by meets_dependency? to track qualifying array params

Fix

Introduces Grape::Validations::ParamScopeTracker — a lightweight object that holds all per-request mutable state in a Fiber[FIBER_KEY] entry:

  • Endpoint#run_validators calls ParamScopeTracker.track {} to set up a fresh tracker for each request, restoring the previous value on exit (reentrant/nested-safe via ensure)
  • AttributesIterator#store_indices writes current and ancestor array indices into the tracker
  • ParamsScope#full_name and #qualifying_params read from the tracker instead of @index/@params_meeting_dependency

Fiber[] (Ruby 3.0+) is used instead of Thread.current[] so that fiber-based servers (e.g. Falcon with async) correctly isolate per-request state within each fiber rather than sharing it across all fibers on the same thread.

Both trackers use {}.compare_by_identity so ParamsScope objects are keyed by object identity, not value equality.

Test plan

  • spec/grape/validations/param_scope_tracker_spec.rb — new unit tests covering lifecycle, reentrance, index/qualifying-params storage
  • Full suite (bundle exec rspec) passes — 2221 examples, 0 failures

🤖 Generated with Claude Code

…ramScopeTracker

ParamsScope instances are shared across requests (definition-time objects).
The mutable `@index` and `@params_meeting_dependency` ivars were being
written during request processing, creating a race condition under concurrent
requests.

Introduces `ParamScopeTracker` which stores all per-request mutable state
(array indices + qualifying params) in a `Fiber[FIBER_KEY]` entry, isolating
each request's state from others. `Endpoint#run_validators` sets up the
tracker via `ParamScopeTracker.track {}`. `AttributesIterator#store_indices`
writes to it; `ParamsScope#full_name` and `#qualifying_params` read from it.

Uses `Fiber[]` (not `Thread.current[]`) so fiber-based servers (e.g. Falcon)
correctly isolate per-request state within each fiber.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

Danger Report

No issues found.

View run

@dblock
Copy link
Member

dblock commented Feb 28, 2026

👏

@dblock dblock merged commit f2fc392 into master Feb 28, 2026
86 of 196 checks passed
@dblock dblock deleted the fix_param_scope_thread_safety branch February 28, 2026 15:58
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