steveklabnik/request_storeのコードリーディング。
Thread.currentはスレッド間では状態を共有しないものの、同一スレッドの異なるリクエスト間では状態を共有してしまいます。
例えばアプリサーバのスレッドを1にして、以下のようなThread.currentのグローバル変数の値を増加させる処理を書いたとすると、Thread.current[:cnt]はリクエストがある度に増加してしまいます。
class HogeController < ApplicationController
def index
Thread.current[:cnt] ||= 0
Thread.current[:cnt] += 1
puts Thread.current[:cnt]
end
end
そういう意図の処理であれば良いですが、リクエストごとのグローバル変数を書きたい場合は意図に反した動きになります。
RequestStoreを使うとRackミドルウェアの仕組みを使ってThread.currentの値をリクエストごとにリセットするため、安全にThread.currentを利用することが出来ます。
コードを読むと、Railtieでミドルウェアを設定しており、gemを読み込んだだけでRackミドルウェアが差し込まれます。
module RequestStore
class Railtie < ::Rails::Railtie
initializer "request_store.insert_middleware" do |app|
if ActionDispatch.const_defined? :RequestId
app.config.middleware.insert_after ActionDispatch::RequestId, RequestStore::Middleware
else
app.config.middleware.insert_after Rack::MethodOverride, RequestStore::Middleware
end
...
end
end
end
差し込まれるRackミドルウェアはRequestStore.begin!→アプリケーションの処理→end!, clear!という流れになっています。
module RequestStore
class Middleware
def initialize(app)
@app = app
end
def call(env)
RequestStore.begin!
@app.call(env)
ensure
RequestStore.end!
RequestStore.clear!
end
end
end
clear!ではThread.current[:request_store]を {} でリセットしており、begin!、end!はThread.current[:request_store_active]の状態を変えています。
module RequestStore
def self.store
Thread.current[:request_store] ||= {}
end
def self.clear!
Thread.current[:request_store] = {}
end
def self.begin!
Thread.current[:request_store_active] = true
end
def self.end!
Thread.current[:request_store_active] = false
end
def self.active?
Thread.current[:request_store_active] || false
end
...
def self.fetch(key, &block)
store[key] = yield unless exist?(key)
store[key]
end
def self.delete(key, &block)
store.delete(key, &block)
end
end