Nette SafeStream
Nette SafeStream は、ファイルの読み書きのすべての操作が切り離されて行われることを保証します。つまり、まだ完全に書き終えていないファイルを読みはじめるスレッドも、同じファイルを重ねて書き込む複数のスレッドもありません。
インストール:
composer require nette/safe-stream
何の役に立つのか
切り離された操作は実のところ何の役に立つのでしょうか。ファイルに繰り返し書き込み、そこから同じ文字列を読み出す単純な例から始めましょう。
$s = str_repeat('Long String', 10000);
$counter = 1000;
while ($counter--) {
file_put_contents('file', $s); // 書きます
$readed = file_get_contents('file'); // 読みます
if ($s !== $readed) { // 確かめます
echo 'strings are different!';
}
}
echo 'strings are different!'
の呼び出しは決して起こらないように見えるかもしれません。ところが逆です。このスクリプトをブラウザの
2 つのタブで同時に走らせてみてください。エラーはほとんどすぐに起こります。
一方のタブが、もう一方がまだ完全に書き終えていない瞬間にそのファイルを読むので、中身が欠けたものになるのです。
ですからこのコードは、同時に何度も(つまり複数のスレッドで)実行されると安全ではありません。インターネットでは珍しいことではありません。サーバーはしばしば大勢の利用者に同時に応えるからです。複数のスレッドで実行されてもアプリケーションが確かに働くようにすること(スレッドセーフであること)が欠かせません。さもないとデータが失われたり、見つけにくいエラーが起きたりします。
とはいえご覧のとおり、PHP のネイティブのファイルの読み書きの関数は、切り離されてもいなければアトミックでもありません。
SafeStream の使い方
SafeStream は、標準の PHP
の関数を使って切り離されてファイルを読み書きできる安全なプロトコルを作ります。ファイル名の前に
nette.safe:// を付けるだけです。
file_put_contents('nette.safe://file', $s);
$s = file_get_contents('nette.safe://file');
SafeStream は、一度にひとつのスレッドだけがそのファイルに書けるようにします。ほかのスレッドは列に並んで待ちます。書いているスレッドがなければ、いくつのスレッドでも並行してそのファイルを読めます。
このプロトコルは、ふつうの PHP の関数すべてと一緒に使えます。たとえば次のようにです。
// 'r' は読み取り専用で開くことを意味します
$handle = fopen('nette.safe://file.txt', 'r');
$ini = parse_ini_file('nette.safe://config.ini');
できないこと
SafeStream はファイルの中身の読み書きを切り離しますが、すべての操作をアトミックにできるわけではありません。次の境目を覚えておいてください。
- ファイルの情報は切り離されません。
file_exists()、filesize()、is_file()のように、メタデータを尋ねるだけの関数は錠の仕組みに加わりません。ほかのスレッドが今書いている最中のファイルの情報を返すことがあります。 - Windows で開いているファイルを消すこと。 Unix と違い、Windows
ではほかのスレッドが今開いているファイルを消せないので、
unlink('nette.safe://file')は失敗することがあります。 - 書き込みが途中で終わったときの自動の巻き戻し。 書き込みが途中で失敗すると(たとえばディスクがいっぱいになると)、ファイルを閉じるときに SafeStream はそれを書きはじめる前の大きさまで切り詰めるので、中途半端に書かれたデータが残ることはありません。
新しい版へ上げるなら、アップグレードのページをご覧ください。