同一セッションの振る舞い

今まで知らなかったんですが、ASP.NETは同一セッションかどうかを判断して、リクエストを直列化するようです。つまり、同一セッションで複数同時にリクエストを投げると、最初のリクエストの処理が終わるまで待たされるという具合です。

これは、セッション管理はInProcとStateServerがありますが、どちらを使っても矛盾しないように設計されているためだと思います。SateServerを使うとセッション情報を永続化するためにシリアライズ/デシリアライズが必要になります。このシリアライズをどのタイミングで行なうか?がポイントなんでしょうかね。セッション情報が変わる度にシリアライズしていたらパフォーマンス的に破綻しそうですから、リクエスト単位(リクエストの最初と最後?)でシリアライズ/デシリアライズするのが現実的と判断してんでしょう。

で、リクエスト単位のシリアライズとなると、リクエストの途中で、同一セッションの情報が他のリクエストから変更されるとセッション情報の不整合が発生してしまいます。なので、同一セッションのリクエストは直列化されて処理するという方針によって、このセッション情報の不整合問題を回避しているということなのでしょう。

一言でいうと「同一セッションにおいては、リクエストセーフ」だということ。ASP.NETって賢いですね。

同一セッションの振る舞い(2)

同一セッションでは、リクエストセーフになるという話の続編です。リクエストセーフはASP.NET独自で処理しているので、飛んできた複数のリクエストはASP.NETで一旦受けておいて、そのリクエストを直列に処理するという方法を取っているようです。この辺の詳しい仕様って、どこかに公開されているのでしょうか?

で、本題なのですが、例えば、2つのリクエストが飛んできて、最初のリクエストを処理するのに時間がかかっていて、次のリクエストが待たされている状態では、待たされているリクエストが処理されるとき、BeginRequestやAuthenticateRequestなどのパイプライン処理が省略されるみたいなのです。なので、HttpModuleを独自に実装して、スレッドローカル(スロット)に値を設定したりしてると、このパイプライン省略されたリクエストでは設定しているはずの値が入ってないということで問題になりました。(私は、ログインユーザのコンテキスト情報としてスレッドローカルに設定していたのです。)

これを回避するには、AuthenticateRequestなどでは独自のコンテキストをスレッドに設定するようなことはせず、IPrincipalに埋め込むべきということです。パイプラインは省略され、スレッドIDも違うリクエストなのですが、このプリンシパル情報はちゃんと引き継ぐようになっているのです。(でないとつかえませんよね。)

なので、結論は、AuthenticateRequestなどのパイプラインで独自のコンテキスト情報をスレッドに埋め込まない。この場合、独自のプリンシパル(IPrincipalの実装クラス)を定義してそこに設定すべきということ。そして、コンテキスト情報は、ページのOnInitイベントなどのタイミングで、独自のプリンシパル(Page.Context.Userプロパティ)から値を取得して設定すればよいでしょう。(例えば、Pageのスーパークラスなんかで。)