EC-CUBE2.11(session_set_save_handler)+Ajaxでセッションがうまくいかない件。

2日悩んでまだうまくいっていないことですが、とりあえずこれまでわかったことをメモします。
※この情報は間違っている可能性が高いです。

EC-CUBEでは2.11ではセッション情報をデータベースに保存している。
通常、商品をカートに入れる際にはカートのページにリダイレクトされ、セッション情報にカートの内容が保存される。

カートの追加をAjaxで実装したところ、連続して複数の商品をカートに追加すると、タイミングによってカートに追加されない商品があるという現象が起きた。EC-CUBE2.11ではSC_Helper_Sessionクラスにてsession_set_save_handlerが呼ばれ、セッションに対して起こるイベント用のようなものが定義されて、オリジナルの処理(データベースへセッション情報を書く処理)がかかれている。

こうすることによって単純に$_SESSIONにプロパティをセットするだけでデータベースにセッション情報を残すことができる。

原因を探っていくと、どうやら$_SESSIONを操作した際に、その都度登録した処理(データベースへ書き込む処理)が呼ばれるわけではないことで、たとえばその処理の中で一度$_SESSIONを読み込み、それに対して何かを付けたし、そして再度$_SESSIONに書き込むというような処理を入れることが原因のようだ。

EC-CUBEではカートにある商品をID(連番の)で処理しているようなのだが、次の商品がカートに追加されるたびに一番最後のID(数字)に1を足して処理している。そのためカートに追加する際に一度カートの内容を読み込み、追加し、書き込むという処理をする。

Ajaxを使うことで「読み込み」「読み込み」「書き込み」という処理がありえるので、商品が0のときは「次はID:1」「次もID:1」「IDが二つかぶるので2つめのみ追加」となる。

通常は「読み込み」から「書き込み」まで同じ行に対する処理をロックできるので問題は起こらないのだが、読み込みは読み込み、書き込みは書き込みとデータベースへのアクセスが別々になっていることでこのようなことになってしまっている模様。

その辺のトランザクションを何とかするか。セッションを連番で処理するのを何とかするか。

どうすれば最善か。まだ考察中です。

追記(2011/11/10)

Ajaxを使うことで「読み込み」「読み込み」「書き込み」という処理がありえるので、商品が0のときは「次はID:1」「次もID:1」「IDが二つかぶるので2つめのみ追加」となる。

さらに調査した結果、こういうことではないようだ。

IDがかぶるから上書きされるのではなく、セッションが書き込まれるときには
「すべてを読み込み」→「変更(追加)」→「すべてを書き込み」となるので、
たとえばAが入っているセッションにB,Cを書き込みたい場合
「A」を読み込んでそれに「B」を足して「A,B」となり、それを書き込む前に「A」を読み込み「C」を足して「A,C」となり、最終的に「A,C」となってしまうということらしい。

これはsession_set_save_handlerでデータベースを使う際にまれにある現象らしい。
http://xoops.ec-cube.net/modules/newbb/viewtopic.php?viewmode=thread&topic_id=8247&forum=2&post_id=39923#forumpost39923
http://svn.ec-cube.net/open_trac/ticket/571

追記2(2011/11/11)

結局、非同期通信を行う場合、session_set_save_handlerを使うと読み込みと書き込みが入れ子になり、上記のような現象になることは避けられなかった。

また、Ajax側で通信を順番に行うようにすることでこれを回避することができた。
具体的には商品をaddしたときにqueueに追加し、順番に処理していく。

また処理が終わったらカートをリフレッシュしていたのだが、コレもまずかった。

カートのリフレッシュ時には当然最新のカートの情報を取得するのだが、それもこの現象の原因になった。
つまり読み込むだけであっても通信が入れ子になることでセッションの同期が取れず、セッション関連がある通信は常に順番に確実に行う必要がある。

session_set_save_handlerを使用しないような実装も考えたが、EC-CUBEの保守がややこしいことになりそうなのでやめた。

通信をすべて順番に行うことで、問題を解決することにした。

  1. 商品を追加ボタンを押す
  2. queueに追加
  3. queueがひとつだけならqueueを開始
  4. ajaxでpost
  5. javascriptでカートに追加中の表示(追加ボタンをロード中にし、カートに商品を半透明で追加表示)
  6. 応答があったら本当にカートに入っているかを一度チェックする
  7. 成功ならカートに追加済みの表示:失敗ならすべてを元に戻す。
  8. queueがまだあるなら次のqueueを開始し、4へ戻る。

追記3(2011/11/11)

resize_image.phpを動かすと、セッションが上書きされるようだ。なんで、画像をリサイズするためにセッションを操作しているのか不明。。
なんだか動作ももたつくので使用するのをやめた。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です