RubyのThread#[]=メソッドについて
RubyのThread#[]=
メソッドでは、スレッドごとに固有の値を保持することができます。(JavaでいうThreadLocal
のようなものです)
これを利用すると、1スレッド1リクエストに紐づくアプリケーションサーバ(Passengerなどほぼ全てのAPサーバ)では、
1リクエスト中のみ閉じて使用できるスレッドローカル変数として使用することができます。
RubyのThread.current[]メソッドの使用例
以下のように Thread.current[:foo]
と現在のスレッドに代入することで、スレッドローカル変数 foo
を定義できます。
def self.foo
Thread.current[:foo] ||= 0
end
また、Ruby on Railsフレームワークにレコードの操作者(作成者、更新者、削除者)を記録する機能を提供するRecordWithOperatorという gem においても
この Thread#[]=
を使用して実装されています。
https://github.com/nay/record_with_operator/blob/4389d077b0303b956cc211ef439a46a216ae2cc0/lib/record_with_operator/operator.rb#L4
Thread#[]=メソッドの注意点
PassengerやPumaのような一度作成したスレッドを再利用するようなアプリケーションサーバを使用している場合、
スレッドローカル変数が再利用前提のスレッドに紐づくため、アプリケーション側で明示的に破棄しないと
以前のリクエスト(スレッド)で定義した変数が別のリクエストで参照できてしまうので注意が必要です。
Redmineにも以前、Thread#[]=
を使用して現在のユーザを扱うコードがあり、
エラーメッセージに別ユーザの名前が表示されるセキュリティ的なバグの発生事例がありました。
http://www.redmine.org/issues/16685
解決策
アプリケーション側で明示的にThread.currentの値を破棄することで回避できます。
RequestStoreというgemを使用すると Rack::Middlewareの層で、リクエスト毎に
Thread.current
の値をクリアするようになります。
使用例は以下の通りです:
def self.foo
RequestStore.store[:foo] ||= 0
end