LoginSignup
5
3

More than 3 years have passed since last update.

RubyのThread#[]=メソッドの注意点

Posted at

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
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3