PHPのファイルチェック関数のキャッシュについて

こんにちは。笹亀です。

8月に入り暑さが厳しくて外出が辛い日々です。先月(7月)の中旬(14,15,16日)頃に日光の方へアシアルメンバー数名と他社さんと合同で開発合宿に行っておりました。お世話になった宿の方のお話だと「先月に5件も開発合宿のお客様がいらっしゃった」とのことで、開発合宿文化はまだまだあるということもわかり、嬉しくおもいました。

今回は久しぶりにPHPネタになりますが、最近のPHPではまったことについて、自分用のメモの意味でもブログに残そうとおもいます。
PHPの技術もだいぶ成熟した感じのイメージを個人的には持っておりますが、まだまだハマることはたくさんあります(ハマると予想外に原因調査に時間がかかることがおおい^^;)
さて、早速、ハマったことについてご紹介します。

ファイルチェック関数がキャッシュをしてしまうことにより意図した挙動と異なる

サンプルスクリプト

下記のようにサンプルのスクリプトを準備しました。コマンドラインからプロセス常駐型(起動しっぱなし)で30秒毎にステータスを監視してステータスがOKならOKファイルを作成し、ステータスNGならOKファイルを削除するプログラムです。

<?php
// ステータスチェックのOKのときに作成するファイル(デモ用)
$check_ok = "/home/sasa/check_OK";
while (1) {
  
  // ステータスのチェックをする
  $isActive = check_status();
  
  // デバック用にステータスを出力
  echo "----\n";
  echo date('Y-m-d H:i:s') . "\n";
  var_dump($isActive);
  
  // ステータスがFalseだった場合はOKファイル削除する
  if (!$isActive) {
    // ファイルの有無をチェック(あったら削除する)
    if (is_file($check_ok)) {
      // ファイルを削除する
      $cmd = "rm -f " . $check_ok;
      exec($cmd, $ret, $status);
      if ($status) {
       echo $test_file . "の削除に失敗しました";
      }
    }
  } elseif ($isActive) {
    // ファイルの有無をチェック(なかったら作成する)
    if (!is_file($check_ok)) {
      // ファイルを作成する
      $cmd = "touch " . $check_ok;
      exec($cmd, $ret, $status);
      if ($status) {
        echo $test_file . "の作成に失敗しました";
      }
    }
  }
  
  //監視頻度の調整(30秒停止)
  sleep(30);
}
// ファイル内容があるかチェック(ステータスチェックのデモ用)
function check_status() {
  $check_str = file_get_contents('/home/sasa/status_check');
  if ($check_str != '') {
    return true;
  } else {
    return false;
  }
}

コマンドラインから↑のプログラムを実行をし、ステータスがOKの場合(status_checkファイルになにかした文字列が入っていた場合)
f:id:tyatya1229:20180717141510p:plain

↓ステータスをNGの状態に更新をする(status_checkファイルを空にする)

f:id:tyatya1229:20180717141520p:plain

ん?ステータスNGなのに、check_OKファイルが消えない・・・
f:id:tyatya1229:20180717140920p:plain

ステータスがNGなので、check_OKファイルが消えてほしいのに消えていないのはなぜ?

PHPのis_file関数は実行結果をキャッシュしてしまう仕様ため、常駐型で実行をしている状態だと、2回目のis_file関数(29行目)はキャッシュを見てしまい、ファイルが存在していないことになり、ファイルが削除処理まで実行されないということです。
PHP: is_link - Manual

解決方法

解決方法はいくつかあります。

  • exec関数のコマンドラインで実行しているrmとかtouchをやめて、PHP関数のものを利用する
    • unlink関数やtouch関数は内部的にcacheをクリアしているので、こちらの関数を利用するように修正する
  • 明示的にキャシュをクリアする関数を呼び出す
    • clearstatcache関数をwhileの最初で呼び出すように修正する

注意事項

is_file()の他にも、is_dir()やis_link()でも同様の現象が発生するので注意しましょう。

所感

おそらくファイルIOのことを考慮して、使う人がサーバのIOまでケアができるかできないかで考えて、デフォルトをcacheするというのを選んだのかとおもいます。個人的には、デフォルトはキャッシュをFalseでパラメータなどで切り替え可能になっていると使い勝手がいいとおもいました。

いまだにPHPの挙動に悩まさせるんだなぁっと感じるケースでした。