Session Restore Error

APサーバー再起動時にSession Restore Errorに遭遇した。
セッションオブジェクトにモデルクラスを入れてたからだ。
これまでやったJavaのWebアプリではセッションはメモリに入れてたからこういう問題に出会わなかったなあ・・

HowtoAvoidSessionRestoreError
http://wiki.rubyonrails.org/rails/pages/HowtoAvoidSessionRestoreError (2006/3/10)

問題
開発してて、なにか機能を追加した後にページを再読み込みすると、SessionRestoreError?と言うメッセージ(以降はSREと呼ばせてもらう)にしょっちゅう出くわすんだ。手順を細かく書くとこんな感じ:

機能を追加する(もちろんバグがある)
ページを再読み込みする。で、そのバグのせいでブラウザ上で(例えばSyntax Errorとかの)例外が発生する。
バグを直す。
ページを再読み込みする。と、ブラウザでSREが発生。
こうなると、どんなにコードを修正してリロードを繰り返してもSREはなくならない。

ちなみに、バグが含まれないように機能を追加できれば、SREは起こらない。あと、どうも全てのbug-fix-reload tapdanceの後でSREが起こるってわけではなさそう。

原因
SREはセッションを復元できないから発生してる。つまり、Railsがリクエストを受け取ると、すぐにコントローラをディスパッチしようとするんだけど、そのためにまずはコントローラが必要なセッションを復元しないといけない。もし何かの理由でそれができなかったとき、SREは起きる。

明らかに、バグが発生したときに、何かがセッションを駄目にしてる。だけどそれが何なのかが全然分からない。

SREにピシャリとやられたら、結局、自分の手でセッションオブジェクトを消去するしかないだろう。通常のファイルベースのセッションだと(つまり通常の開発環境だと)、下のようなコマンドを打つことになる:

rm $TMP/ruby_sess.*

もし君がDBベースのセッション(HowtoChangeSessionStoreを参照)を使っていた場合、Rakeタスク'purge_sessions_table'を使うことになるだろう:

rake purge_sessions_table

これはとても重要なので、もう一度繰り返す:

SREにピシャリとやられたら、結局、自分の手でセッションオブジェクト(通常はセッションファイル)を消去するしかないだろう。

ただ、もし古いセッションが残っても気にしないのなら、ブラウザを再起動しても問題は解決できる。

普通の解決策
ありがちなSREの理由は、セッションオブジェクトに君の作ったモデルクラス(この後は、例としてArticleを使う)のインスタンスを含めてしまうことだ。Railsは require "article" という行を見るまで、君のクラスのことが分からない。

RailsはAdminコントローラを起動するので、それが article を知っている必要がある。だから、SREを避けるには、次のようにすればいい:

class AdminController < ApplicationController
model :article # INSERT THIS LINE

# The rest of your code...
end

この model :article という行が、Railsに article を先に知っておく必要があることを教えていて、おかげでもしセッションがそれを使っていたとしても先に備えておける。 WhenToUseTheModelMethod?も参照のこと。

もしそれでもうまくいかなかったら...
上で述べたような問題(bug-fix-reload tapdanceの後でSREが発生すること)とは別の何かが起きてると言うことだ。僕は ApplicationController にたくさんの model 節を書いて、確かに全てのモデルをコントローラが知ってるようにした。

結局、SREが僕の開発サイクルを邪魔するのがホントいやになって、思い切った手段をとることにした。つまり:

セッションの中身をもっとずっと単純にした。

そう、Userオブジェクトをセッションに保存する代わりに、user_idを保存して、リクエストのたびにUserを作り直すことにしたんだ。

それ以来、僕のSREブルースは消えてなくなった。

でもそれってちょっとやり過ぎじゃない?
リクエスト毎に毎回 Userオブジェクトをuser_idから作り直すと言うのは確かにやりすぎなときもあればそうでもないときもある。はっきり言って、気にしない。 DBはキャッシュのおかげで十分早いだろう。 Perhaps later on I can spinkle some Rails caching magic on my code to cache it at that level. もしかしたら製品版ではUserオブジェクトをセッションに保存するような仕様に戻すかもしれない。

大事なことは、この手段は実際の開発で採用できる程度に手軽で、さらにこのむかむかする膿を開発サイクルから取り除くことができるってことだ。

また、AlisdairMcDiarmidがくれた次の重要なコメントには注意しておく価値がある:

セキュリティの観点からもUserオブジェクトをセッションに保存しないことは重要だ。Userが後で削除されたり、属性が変更されたりしたときに、セッションにオブジェクトが保存されていると無効なオブジェクトにアクセスされる可能性がある。これはDBにアクセスするプログラムが他にも存在する場合に、特に問題になる。

最後に
だれかSREを叩きのめしたことがある人は、どうか僕にもその方法を教えて欲しい (gsinclair at gmail dot com)。また、君が結局僕と同じようにしたとしても、そのことを教えてほしい。僕が、このRails王国でこんな面倒を被ったたった一人の男なのかもしれないけど。

    • GavinSinclair