47
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PHPがstatシステムコールの結果をキャッシュしている件

Last updated at Posted at 2016-10-25

PHPのstatキャッシュ

PHPにはCurrentStatFileという内部管理用の変数があり、直前のstatシステムコールの結果を1件だけ覚えています。これはたとえば次のような状況で使われます。

<?php

mkdir("foo");
var_dump(is_dir("foo")); // bool(true)
var_dump(is_dir("foo")); // bool(true)

パッと見ではわかりませんが、実は上記プログラムのis_dir()の内部処理は1回目と2回目とで異なっています。1回目は真面目にstatシステムコールを呼んでfooがディレクトリかどうかOSに問い合わせているのですが、2回目の呼び出しではシステムコールを呼ばず、直前のシステムコールが返した値を使い回すという挙動になっています。本稿ではこのキャッシュのことをstatキャッシュと呼ぶことにします。

以下の関数がstatキャッシュを利用するようです。

影響を受ける関数を以下に示します。 stat(), lstat(), file_exists(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), filectime(), fileatime(), filemtime(), fileinode(), filegroup(), fileowner(), filesize(), filetype(), および fileperms().

http://php.net/manual/ja/function.clearstatcache.php

statキャッシュは暗黙的にクリアされることがある

statキャッシュは必要に応じてクリアされたりもします。

<?php

mkdir("foo");
var_dump(is_dir("foo")); // bool(true)
rmdir("foo");
var_dump(is_dir("foo")); // bool(false)

今度のプログラムではrmdir()でディレクトリを削除していますが、2回目のis_dir()は正しくディレクトリの削除を認識しています。実はrmdir()の実行時にstatキャッシュがクリアされるので、古いキャッシュのせいで混乱するようなことはないというわけです。

rmdir()を含め、下記の関数を呼び出すことでstatキャッシュのクリアが行われます。

  • unlink
  • rename
  • rmdir
  • chdir(statキャッシュ対象が相対パスのときのみクリアされる)
  • chroot
  • chgrp
  • lchgrp
  • chown
  • lchown
  • chmod
  • touch(対象がfile:///のときだけ)

これは残念ながらドキュメント化されていない情報で、PHP 7.0.12時点のPHPソースコードから私が読み取ったものです(unlinkについてはclearstatcacheの説明に書いてありました)。

statキャッシュの不整合と明示的なクリア

上記の関数以外の関数を利用してファイルシステムの更新を行うと古い状態のstatキャッシュが更新されないまま残ってしまい、キャッシュの不整合が発生することがあります。先ほどのプログラムを少し変更してみましょう。

<?php

mkdir("foo");
var_dump(is_dir("foo")); // bool(true)
system("rmdir foo");
var_dump(is_dir("foo")); // bool(true)

system()rmdirコマンドを呼び出してディレクトリを削除していますが、is_dir()はディレクトリが消えたことに気づいていません。外部システム呼び出しをしてしまうとstatキャッシュがクリアされず、このような不整合状態が起きてしまうのです。

こわいですねー。恐ろしいですねー。

もちろん対策はあります。statのキャッシュが信用ならないことをプログラマが知っている場合、clearstatcache()を呼び出すことでこのキャッシュをクリアすることができます。

<?php

mkdir("foo");
var_dump(is_dir("foo")); // bool(true)
system("rmdir foo");
clearstatcache();
var_dump(is_dir("foo")); // bool(false)

このあたりはi_ogiさんが2008年にまとめた頃からほとんど変わっていないと思いますが、案外知られていないように思います。

47
33
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?