Nette SafeStream

Nette SafeStream guarantees that every file read and write operation occurs in isolation. This means that no thread will start reading a file that hasn't been fully written yet, nor will multiple threads overwrite the same file.

Installation:

composer require nette/safe-stream

What Is It Good For?

What are isolated operations actually good for? Let's start with a simple example that repeatedly writes to a file and then reads the same string from it:

$s = str_repeat('Long String', 10000);

$counter = 1000;
while ($counter--) {
	file_put_contents('file', $s); // write it
	$readed = file_get_contents('file'); // read it
	if ($s !== $readed) { // check it
		echo 'strings are different!';
	}
}

It might seem that the call echo 'strings are different!' can never occur. The opposite is true. Try running this script in two browser tabs simultaneously. The error will occur almost immediately.

One of the tabs will read the file at a moment when the other hasn't finished writing it completely, so the content will be incomplete.

Therefore, the code is not safe if executed multiple times concurrently (i.e., in multiple threads). This is not uncommon on the internet, as servers often respond to a large number of users simultaneously. Ensuring that your application works reliably even when executed in multiple threads (thread-safe) is crucial. Otherwise, data loss and hard-to-detect errors can occur.

However, as you can see, PHP's native file read and write functions are not isolated or atomic.

How to Use SafeStream?

SafeStream creates a secure protocol through which files can be read and written in isolation using standard PHP functions. You just need to prefix the filename with nette.safe://:

file_put_contents('nette.safe://file', $s);
$s = file_get_contents('nette.safe://file');

SafeStream ensures that at most one thread can write to the file at a time. Other threads wait in a queue. If no thread is writing, any number of threads can read the file in parallel.

All common PHP functions can be used with the protocol, for example:

// 'r' means open for reading only
$handle = fopen('nette.safe://file.txt', 'r');

$ini = parse_ini_file('nette.safe://config.ini');

Limitations

SafeStream isolates reading and writing of file contents, but it can't make every operation atomic. Keep these boundaries in mind:

  • File information isn't isolated. Functions that only query metadata, such as file_exists(), filesize(), or is_file(), don't take part in the locking. They may return information about a file that another thread is currently writing.
  • Deleting an open file on Windows. Unlike Unix, Windows won't let you delete a file that another thread currently has open, so unlink('nette.safe://file') can fail.
  • Automatic rollback of incomplete writes. If a write fails midway (for example the disk fills up), then when the file is closed SafeStream truncates it back to the size it had before writing started, so it never leaves partially written data behind.

If you are upgrading to a newer version, see the upgrading page.

version: 3.x 2.x