ActiveSupport::CurrentAttributes的Current类属性是否会被未完成请求的新请求覆盖?
ActiveSupport::CurrentAttributes Get Overwritten by Parallel Requests? Great question—this is a super common pitfall with ActiveSupport::CurrentAttributes when dealing with parallel request processing in Rails! The short answer is: Yes, it can get overwritten if you don’t account for your server’s concurrency model or have long-running/async code that outlives the request lifecycle.
Let’s break this down with your example and different scenarios:
How CurrentAttributes Works Under the Hood
ActiveSupport::CurrentAttributes uses thread-local storage (Thread.current) to isolate state per request. Rails automatically resets these attributes at the start and end of each request via the ActionDispatch::CurrentAttributes middleware. This works fine in most standard setups—but it has critical edge cases.
Scenario 1: Multi-Threaded Servers (e.g., Puma)
By default, Puma uses a thread pool where each thread handles one request at a time. In this case, parallel requests won’t overwrite each other’s Current state because each request runs in a separate thread, and thread-local storage is unique to each thread.
But here’s the catch: if your controller action has long-running async code that outlives the request (like a background job or delayed callback), things break. For example:
# Your Current class class Current < ActiveSupport::CurrentAttributes attribute :user_id end # Controller action class DataController < ApplicationController before_action { Current.user_id = current_user.id } def process_data # Simulate a slow, synchronous operation sleep 10 # Or worse: fire a background job that relies on Current DataProcessingJob.perform_later render json: { status: "started" } end end # Background Job (bad practice!) class DataProcessingJob < ApplicationJob def perform # This might pick up a different user's ID if the thread was reused Log.create!(user_id: Current.user_id, action: "processed_data") end end
When the request finishes, Rails resets Current for that thread. If the thread is reused for another request before the job runs, the job will read the new request’s user_id instead of the original one.
Scenario 2: Coroutine-Based Servers (e.g., Falcon)
Servers like Falcon use coroutines (fibers) that share the same thread. Since Thread.current is shared across all coroutines in a thread, parallel requests running in different coroutines will overwrite each other’s Current state—even during the request lifecycle. This is a much more immediate problem, as two concurrent requests can clobber each other’s attributes mid-processing.
How to Prevent Data Errors
Here are the most reliable fixes:
Explicitly pass parameters to async code: Never rely on
CurrentAttributesin background jobs or delayed callbacks. Instead, pass the required data directly:# Good practice: pass user_id to the job DataProcessingJob.perform_later(current_user.id) class DataProcessingJob < ApplicationJob def perform(user_id) Log.create!(user_id: user_id, action: "processed_data") end endUse coroutine-aware storage for coroutine servers: If you’re using a coroutine-based server, replace thread-local storage with fiber-local storage. You can extend
CurrentAttributesto useFiber.currentinstead ofThread.current, or use Rails’ActiveSupport::IsolatedExecutionState(added in Rails 7.1) which handles both threads and fibers.Avoid long-running synchronous operations in controllers: If possible, offload slow work to background jobs entirely. This keeps request lifecycles short and reduces the chance of thread reuse causing issues.
内容的提问来源于stack exchange,提问作者Cezar T

