PHPでセッションの排他ロックを解除する

排他ロック解除

session_start()について

Webを探してもあまり記事が拾えなかったので、放流しておく。

PHPでWebからAPIのモジュールを呼ぶシステムを作っていたら、モジュールの呼び出しが逐次処理になっていることが分かった。

そのシステムでは、セッションを使ってデータの読み書きをしている。
どうやらsession_start()を呼んだあと、モジュールの実行が終わるまで、セッションに排他ロックがかかってしまう模様。

PHPのセッション関連の調べ物をする時に、この辺りの話がでてこなかったので、今まで気付かなかった。

session_start()の説明を見ても、やっぱりそんなこと書いてない。
…と思っていたが、フォーラムのところに、まさにその話が出ていた。

dave1010 at gmail dot com 16-Dec-2010 05:35
PHP locks the session file until it is closed. If you have 2 scripts using the same session (i.e. from the same user) then the 2nd script will not finish its call to session_start() until the first script finishes execution.

If you have scripts that run for more than a second and users may be making more than 1 request at a time then it is worth calling session_write_close() as soon as you've finished writing session data.

<?php
// a lock is places on the session, so other scripts will have to wait
session_start();

// do all your writing to $_SESSION
$_SESSION['a'] = 1;

// $_SESSION can still be read, but writing will not update the session.
// the lock is removed and other scripts can now read the session
session_write_close();

do_something_slow();
?>

Found this out from http://konrness.com/php5/how-to-prevent-blocking-php-requests/

PHP: session_start - Manual

要約するとこんな感じか。

同じセッション(つまり同じユーザからの)を使用して、2つのスクリプトを呼ぶ場合、セッションの同時書き込みを回避するための排他ロックがかかる。最初のスクリプトが実行を終了するまで(排他ロックが解除されるまで)、2つ目のスクリプトは待ち状態になる。

複数のリクエストに対応するには、セッションデータを書き終えたすぐあとに、session_write_close()を呼ぶ必要がある。

つまり並列処理を実現するには、セッションの書き込みが終了した時点で、session_write_close()を呼ぶ。そこで排他ロックを解除すれば、続くリクエストが待ち状態になることなく実行される。

session_write_close()について

session_write_close()の説明には、その旨が書いてあった。

説明

void session_write_close ( void )

現在のセッションを終了し、セッションデータを書き込みます。

セッションデータは、 session_write_close() をコールしなくても、スクリプト終了時に保存されます。しかし、 セッションデータは、同時書き込みを防ぐためにロックされるため、 ある時点であるセッションの処理ができるスクリプトは、1つだけです。 セッションでフレームセットを使用する場合、 このロックのためにフレームがひとつずつロードされるような経験をするでしょう。 セッションへの全ての変更が行われるとすぐにセッションを終了することにより、 全てのフレームのロードに要する時間を減らすことができます。

PHP: session_write_close - Manual

う〜む…これはsession_start()のところにも書いておいてほしいものだ。
結構クリティカルな話だと思うんだけどなぁ。

というわけでセッション書き込み終了時点で、session_write_close()を呼ぶようにして、並列処理を実現することができた。
セッション読み書きしてる箇所が多かったので、修正にちと苦労したけど。

セッションを扱う時は、この事を考慮した実装をしないといけないな。
あとマニュアルの下のフォーラムにも目を通しておくこと。

参考

[PHP]セッションは排他ロックをす: n-streamからのお知らせ

セッションデータのロックと競合: FileMaker Pro 7~11 によるWeb開発覚え書き

プロになるための PHPプログラミング入門
星野 香保子
技術評論社
売り上げランキング: 16,049
  • segavvy

    なかなか興味深いお話です。

    私はPHPはど素人なので間違ってるかもしれませんが、入門書レベルでこの辺の話が解説されていないのであれば、PHPのプログラマさんの大半はセッション排他の意識なくコードを書いていることになるのかもしれませんね。もしそうなら、session_write_close()を呼び出して排他を解除すれば解決!という解決手段は、実はもっと深い問題を掘り起こしてしまう可能性がありそうです。

    セッション排他の意識なくコードを書いているということは、「今注目している処理が動いているまさにこの瞬間にも、同時に動く同一セッションの別の処理によってセッション内の情報が書き換えられているかもしれない」という並列プログラミングの基礎的な配慮が欠落していることになります。

    プログラムそのものが並列して動かなければ、別に並列プログラミングの配慮が欠落していてもなんの問題もありませんが、このsession_write_close()を使い始めたら処理は並列で動き始めるでしょうから、並列プログラミングの扉を開けてしまうことになりそうです。

    もしそうなると、session_write_close()を知るのと同時に、並列プログラミングの考え方も知る必要があります。そうしないと、タイミングによってセッション情報が破壊したり、それによって誤動作や脆弱性を産み出すようなシステムが出来てしまいそうです。一人でリロード連打してるだけで死んでしまうようなシステムも出てくるかもしれません。

    杞憂ならいいのですが...

    • 自分がいくつか所有している入門書には、セッションの開始と破棄の方法などは書かれていますが、セッションが排他される旨やsession_write_close()についての記載は見当たりませんでした。
      その点も含め、世の中のPHPプログラマさんから何か反応があればと思い、記事をポストした次第です。

      確かにsession_write_close()を使うことによって、並列プログラミングの熟知が必要になってきますね。自分はまだまだ勉強不足なので、もう少し掘り下げていきたいところです。

      ちなみにclose後は、再度session_start()を呼ばない限り、セッションデータの書き換えを行うことはできません。セッション読み書きのタイミングでsession_start()、session_write_close()を呼ぶような実装であれば、セッション情報の破壊も無いものと認識しています。

  • segavvy

    「セッション情報が破壊」という言葉がイマイチわかりにくかったですね、ごめんなさい。

    私が言いたかったのは、複数の処理が同時にセッションに書きに行って物理的に破損させちゃうようなことではありません(星影さんの言うように、そうならないために排他の仕組みがあるんだと思います)。そうではなく、並列処理に配慮しないことによる実装ミスによって、セッションの格納値を正しくない値で壊してしまうことを指しています。

    例えば、PHPは文法すら全く分からないので日本語で書くと、

    1: session_start()
    2: 「セッションに格納している『元気玉チャージ時間』」を取り出して変数zikanに格納
    3: session_write_close()
    4: 元気玉を渡す処理を実行し、渡した元気玉1個当たり22時間をzikanから引き算
    5: session_start()
    6: 「セッションに格納している『元気玉チャージ時間』」にzikanを格納して更新
    7: session_write_close()

    みたいなコードを書いた場合、処理が複数同時に動いてしまうと元気玉がたくさんゲットできちゃうことがあります。そのようなことを伝えたかった次第です。

    よろしくお願いします。

    • 補足説明ありがとうございます。
      私の解釈が誤っていたようで申し訳ありません。

      複数同時処理によって、想定とは異なる処理の値が入ってしまう可能性があるということですね。
      元気玉たくさんゲットできたら、こちらとしてはウハウハですけど(;・∀・)

  • だんご

    逆に自作のハンドラで排他処理かかってないと厄介なことになることを経験しました・・・

    セッションに書き混む前に次のスクリプトが書き込んでセッション破壊!