Passenger in development で Thread.local 使うとorz

current_user というメソッドがある。これは、現在接続中のユーザーオブジェクトを表す。

  def current_user
    @current_user ||= WjUser.find(session[:wj_current_user_id]) rescue WjUser.anonymous
  end

で WjUser.anonymous は、匿名ユーザーを表す。頻繁に使うので、1スレッドごとにキャッシュするようにしている。

  def self.anonymous(reload = false)
    if reload || Thread.current["wj_user.anonymous"].nil?
      Thread.current["wj_user.anonymous"] = self.find_by_login_name(PRIMITIVE_ANONYMOUS)
    end
    Thread.current["wj_user.anonymous"]
  end


で、こんなViewコードを書いてみた。

<%=h WjUser.id %><br/>
<%=h WjUser.anonymous(false).class.id %><br/>
<%=h current_user.class.id %><br/>
<%=h WjUser.id %><br/>
<%=h current_user.kind_of?(WjUser) %><br/>
<%=h current_user.instance_of?(WjUser) %><br/>

結果。初回は1行目と1行目のidが一致するんだけれども2回目以降は、次のように

16579060
16154770
16154770
16579060
false
false

リロードするたびに、WjUser.id は変化する。この理由は思うに、プロセスモデルのせいだと思われる。

Apache preforkだと、リクエストを処理しているスレッドは変わらないので(preforkだから1スレッドで動くはず、recycleされない限り同じ)、初回のリクエストのオブジェクトが保持される。development 環境で動かしているせいで、WjUserがRailsのdependenciesによって毎回定義されてしまう。したがって、初回のWjUserと二回目移行のWjUserが違うオブジェクトになってしまって、Thread.localに保存されたオブジェクトは初回のWjUserを指してしまう。

mongrel の場合は、毎回スレッドを生成して割り当てる(ただしRailsの場合、同時に動くのは1スレッド)ので、毎回WjUserが生成されると同時に、Thread.localも毎回異なるので、Thread.localに保存されたオブジェクトは同じWjUserを指す。

わかりにくいんだけれど。passenger の場合は、リクエストの生存期間 != スレッドの生存期間で、mongrelの場合はリクエストの生存期間 = スレッドの生存期間

となるとモデルオブジェクトを、リクエストの生存期間でキャッシュするにはcurrent_userと同じくControllerのインスタンスオブジェクト使うしかないですか。。。そりゃそうだ。Threadで誤魔化そうとしていた自分が悪い。