27
file_put_contents ( "file", "data", LOCK_EX )

for writing (which means - aquire lock and write)

file_get_contents ( "file", LOCK_EX )

for reading (which means - aquire lock and then read)

will it throw exception? raise an error? block until lock is aquired? or at least - should it? is there a chance that php will behave like this one day?

EDIT: i know that it is possible to use rename - i'd like to know answer to this...

Kamil Tomšík
  • 2,415
  • 2
  • 28
  • 32
  • Of course, you could `fopen` just to `flock`, once you have this lock, you can use `file_get_contents`, since acquiring a `LOCK_SH` merely prevents any other PHP process from acquiring `LOCK_EX`, so `file_get_contents` doesn't return a partially-written/modified file (provided all PHP file access is strictly using `flock` too, of course). – Lee Kowalkowski Sep 15 '13 at 20:48

2 Answers2

48

Since this answer is long, here's the summary: No, file_get_contents() is not atomic as it does not respect advisory locks.

About file locks in PHP:

In PHP, while on a *nix platform, filesystem locking is advisory only. Per the docs (Emphasis mine):

PHP supports a portable way of locking complete files in an advisory way (which means all accessing programs have to use the same way of locking or it will not work). By default, this function will block until the requested lock is acquired; this may be controlled (on non-Windows platforms) with the LOCK_NB option documented below.

So, as long as all of the processes that are accessing the file use this method of locking, you're fine.

However, if you're writing a static HTML file with a sane webserver, the lock will be ignored. In the middle of the write, if a request comes in, Apache will serve the partially written file. The locks will have no effect on the other process reading the lock.

The only real exception is if you use the special mount option of -o mand on the filesystem to enable mandatory locking (but that's not really used much, and can have a performance penalty).

Have a read on File Locking for some more information. Namely the section under Unix:

This means that cooperating processes may use locks to coordinate access to a file among themselves, but programs are also free to ignore locks and access the file in any way they choose to.

So, in conclusion, using LOCK_EX will create an advisory lock on the file. Any attempt to read the file will block only if the reader respects and/or checks for the lock. If they do not, the lock will be ignored (since it can be).

Try it out. In one process:

file_put_contents('test.txt', 'Foo bar');
$f = fopen('test.txt', 'a+');
if (flock($f, LOCK_EX)) {
    sleep(10);
    fseek($f, 0);
    var_dump(fgets($f, 4048));
    flock($f, LOCK_UN);
}
fclose($f);

And while it's sleeping, call this:

$f = fopen('test.txt', 'a+');
fwrite($f, 'foobar');
fclose($f);

The output will be foobar...

About file_get_contents specifically:

To your other specific question, first off, there is no LOCK_EX parameter to file_get_contents. So you can't pass that in.

Now, looking at the source code, we can see the function file_get_contents defined on line 521. There are no calls to the internal function php_stream_lock as there are when you pass file_put_contents('file', 'txt', LOCK_EX); defined on line 589 of the same file.

So, let's test it, shall we:

In file1.php:

file_put_contents('test.txt', 'Foo bar');
$f = fopen('test.txt', 'a+');
if (flock($f, LOCK_EX)) {
    sleep(10);
    fseek($f, 0);
    var_dump(fgets($f, 4048));
    flock($f, LOCK_UN);
}
fclose($f);

In file2.php:

var_dump(file_get_contents('test.txt'));

When run, file2.php returns immediately. So no, it doesn't appear that file_get_contents respects file locks at all...

edigu
  • 9,878
  • 5
  • 57
  • 80
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
4

Theory questions work much better on Programmers than here.

At this point, PHP does not support atomic file locking.

Simply put, PHP doesn't support a combined fopen and flock operation, so there will always be a small window of opportunity for another process to lock a file your process has also opened before your process can lock it.

Having said that, flock will, by default, block until the lock is released. Keep in mind ircmaxell's note about advisory locks on Linux/BSD, though.

Note: For the read process, you may want to make it a LOCK_SH instead of LOCK_EX, so that multiple reader threads can lock it at the same time. Writing must always be done using LOCK_EX or risk data corruption.

Note 2: The previous note works because you can only acquire shared locks if no exclusive locks are present, but an exclusive lock requires that no locks of any sort are present before the lock can be acquired.

Community
  • 1
  • 1
Powerlord
  • 87,612
  • 17
  • 125
  • 175
  • -1 for suggesting a perfectly objective question about programming ought to go to Programmers. – Artefacto Feb 04 '11 at 19:30
  • @Artefacto: It was ONLY a polite suggestion, for which he 'may' be correct. Plus, he gave a detailed answer afterwards! Useless move! – Jeach Mar 15 '13 at 17:20
  • @Powerlord, so you are saying if there are many LOCK_SH threads queued for running (and once they are done more gets queued), the LOCK_EX thread will never get a chance to run? – Pacerier Jun 24 '13 at 12:21
  • @Pacerier Unless a thread has already locked a file with `LOCK_EX`, threads can freely obtain a `LOCK_SH` on said file. Incidentally, this kind of locking shouldn't be used on threads that will keep the file open for a long time because anything trying to open it with `LOCK_EX` will be blocked as long as *any* processes have a `LOCK_SH` on it. – Powerlord Jun 25 '13 at 13:49
  • @Powerlord, doesn't that mean that the LOCK_EX thread will starve as long as we have a constant flow of readers (LOCK_SH) ? – Pacerier Jun 26 '13 at 12:24
  • @Pacerier Yes. It's one of the downsides of using `LOCK_SH`. If you know you're going to have lots of readers hitting it constantly, switch the reader code to `LOCK_EX` too... or use a database which are written for this sort of use case. – Powerlord Jun 27 '13 at 14:10
  • @Powerlord, but a database is implemented on top of the primitives LOCK_SH and LOCK_EX. What ways do they employ to ensure that the write does not get starved? – Pacerier Jun 28 '13 at 07:23
  • @Pacerier Depends on the database engine. Berkeley DB and SQLite would suffer from that (maybe), but most other databases (save MySQL on MyISAM tables) support row-level locking. – Powerlord Jul 01 '13 at 02:09
  • 1
    @Powerlord, Re "doesn't support a combined fopen and flock operation" and what about fopen flag `c` and `c+`? – Pacerier Nov 21 '17 at 10:20
  • Flags `c` and `c+` have no affect on flock operation, they only don't truncate the file like `w` and `w+`. (Flags to system call *open()* : `c` - `O_CREAT`, `w` - `O_TRUNC|O_CREAT`, `x` - `O_CREAT|O_EXCL`). – Ko Cour Mar 22 '18 at 15:30