ja ja ja en ja ja ja ja ja ja en ja ja en ja en ja ja ja ja en ja en ja ja ja ja en ja ja ja en ja ja ja en ja en ja ja ja ja ja ja ja ja en nl ja en ja ja en ja en ru ja ja en ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja cv es es ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
require 'fiber' f = Fiber.new do Thread.current[:locale] = :ja puts 'in another fiber' puts "locale: #{Thread.current[:locale]}" end f.resume puts 'in main fiber' puts "locale: #{Thread.current[:locale]}"
in another fiber locale: ja in main fiber locale:
ではファイバーを切り替えても同一スレッドであれば同じ値を返すようにしたいときにはどうすればよいでしょうか?
スレッドローカル変数を扱いたいときは別のメソッドを使う
Thread#thread_variable_getとThread#thread_variable_setを使います。
require 'fiber' f = Fiber.new do Thread.current.thread_variable_set(:locale, :ja) puts 'in another fiber' puts "locale: #{Thread.current.thread_variable_get(:locale)}" end f.resume puts 'in main fiber' puts "locale: #{Thread.current.thread_variable_get(:locale)}"
in another fiber locale: ja in main fiber locale: ja
よく読むとるりまにもちゃんと書いてありますね…。
Thread#[] と Thread#[]= を用いたスレッド固有の変数は Fiber を切り替えると異なる変数を返す事に注意してください。
それがどうしたんですか?
Rails6.1までは、恐らくスレッドローカル変数のつもりでファイバーローカル変数(Thread#[], Thread#[]=)が使われている箇所がありました。例えばCurrentAttributesの設定値はファイバーローカル変数を使っています。
rails/current_attributes.rb at f0506126cb98616444b359162361d2aaaa329f46 · rails/rails
これは、たとえばpumaのようなスレッドベースのアプリケーションサーバのどこかで複数のファイバーを使っていると問題になります。ファイバーAで設定したファイバーローカル変数は(当たり前ですが)ファイバーBでは見えません。これは期待している挙動ではないでしょう。
いまどきのよくあるRailsアプリケーションでは明示的に複数ファイバーを使うことはあまりなさそうに思えます。が、例えば内部でファイバーを使っているgemに依存性があるかもしれません。また、明示的にファイバーを使わなくてもEnumeratorで外部イテレータを使っているところではファイバーが使われています(参考: class Enumerator (Ruby 3.0.0 リファレンスマニュアル) )。外部イテレータもいまどきのよくあるRailsアプリケーションで明示的に使われることは少なそうですが、これも依存しているgemで採用されている可能性はあります。
下記のコードは外部イテレータでファイバーローカル変数とスレッドローカル変数をアサインしたあとに参照したサンプルコードです。アサインしたはずのファイバーローカル変数が参照できていない事がわかります。
o = Object.new def o.each yield Thread.current[:fiber_local] = 'hi' yield Thread.current.thread_variable_set(:thread_local, 'hihi') end e = o.to_enum e.next #=> 'hi' e.next #=> 'hihi' Thread.current[:fiber_local] #=> nil Thread.current.thread_variable_get(:thread_local) #=> 'hihi'
まれに問題になりそうなのはわかったけど…それで?
素直に考えるとRailsの内部でファイバーローカル変数を使っている箇所をスレッドローカル変数に変更すれば良いように思えます。が、みんながスレッドベースのアプリケーションサーバを使っているとは限らないのでした。falconなどのファイバーベースのアプリケーションサーバを使いたい人たちにとってはファイバーローカル変数でないと困ります。
そこで、Rails7.0からは、設定としてスレッドローカル変数を使うかファイバーローカル変数を使うか選択できるようになりました。
Introduce ActiveSupport::IsolatedExecutionState for internal use · rails/rails@540d2f4
デフォルトでは:threadが設定されているので、pumaなどのスレッドベースのサーバを使っている人はRails7.0に上げれば問題は起きなくなりそうです。falconを使いたい人は下記のように明示的にfiberを使うようにする必要があります。
Rails.application.config.active_support.isolation_level = :fiber
そんなすぐRails7.0にあげられないぞ、という人はそれっぽい問題が起きたときにこのことを思い出せるようにしておいてください。そして早く7.0にできるように頑張りましょう。
Rubyでのスレッドローカル変数とファイバーローカル変数の違い
関連したRailsでの問題と解決の事例
Rails本体に関してはRails7.0以降は問題が起きなさそうですが、他のgemでスレッドローカル変数を意図してThread.current#[]=を使っていそうなところがちらほらあり、ちゃんと考えると何らかの対応(ex: Railsのように設定でスレッドローカルとファイバーローカルを切り替える)をしないといけないんだろうなーという気がしています(が、全てのgemで対応するのはなかなか難しそうですね…)。
*1:バックエンドミーティングやバックエンド定例とも呼ばれています
*2:ちなみに去年のアドベントカレンダーも同じように同じようにありがたいお言葉コーナーからの切り出し記事でした https://blog.willnet.in/entry/2020/12/11/100000
willnet 2021-12-23 18:41
エラー通知用ウェブサービス airbrake のメモ
エラー通知用のウェブサービス Airbrake の ruby 用プラグイン …
Rails 3 の I18n について
Ruby on Rails Guides: Rails Internationalization (I18n) API…
Rails 3.0 リリースノート和訳 - 後編
お待たせしました。後編です。一週間で書くつもりが一ヶ月かか…
Railsでログイン状態を保持する方法
(6/20 大幅に加筆修正を行いました)Railsのセッションの有効期…
今日勉強したことをメモメモ 関数 defvar 変数のドキュメントを…
« iCARE Dev Meetup #34で登壇しました Rack::RuntimeがRailsのデフォルトRackミ… »
id:willnet はてなブログPro
べんりで楽しいwebサービスをたくさん作りたい
読者です 読者をやめる 読者になる 読者になる
@netwillnetをフォロー
失敗したテストのログだけを出力するぞ
iCARE Dev Meetup #34で登壇しました
`Thread.current[:hoge]`はスレッドローカル変数を参照していると思いきや実際はファイバーローカル変数だった