flock

(PHP 4, PHP 5, PHP 7, PHP 8)

flock β€” Π‘Π»ΠΎΠΊΠΈΡ€ΡƒΠ΅Ρ‚ Ρ„Π°ΠΉΠ» ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠΌ пСрСносимой Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΉ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ

ОписаниС

function flock(resource $stream, int $operation, int &$would_block = null): bool

Ѐункция flock() Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ ΠΏΡ€ΠΎΡΡ‚ΡƒΡŽ модСль чтСния-записи, которая Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ Π½Π° большСй части Unix-ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌ ΠΈ Π΄Π°ΠΆΠ΅ Π½Π° ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ°Ρ… сСмСйства Windows.

Π‘Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ Ρ‚Π°ΠΊΠΆΠ΅ снимаСт функция fclose() ΠΈΠ»ΠΈ сборщик мусора, ΠΊΠΎΠ³Π΄Π° ΡƒΠ½ΠΈΡ‡Ρ‚ΠΎΠΆΠ°Π΅Ρ‚ ΠΏΠΎΡ‚ΠΎΠΊ stream.

PHP ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ способ ΠΏΠΎΠ»Π½ΠΎΠΉ пСрСносимой Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΉ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ². Π’ΠΎ Π΅ΡΡ‚ΡŒ каТдая ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ°, которая ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ доступ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ, Π΄ΠΎΠ»ΠΆΠ½Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΠ΄ΠΈΠ½ ΠΈ Ρ‚ΠΎΡ‚ ΠΆΠ΅ способ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ, ΠΈΠ½Π°Ρ‡Π΅ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ. По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ функция Π±ΡƒΠ΄Π΅Ρ‚ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π΄ΠΎ Ρ‚Π΅Ρ… ΠΏΠΎΡ€, ΠΏΠΎΠΊΠ° Π·Π°ΠΏΡ€ΠΎΡˆΠ΅Π½Π½Π°Ρ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π°; это ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΈΠ·ΠΌΠ΅Π½ΡΡŽΡ‚ Ρ‡Π΅Ρ€Π΅Π· описанный Π½ΠΈΠΆΠ΅ Ρ„Π»Π°Π³ LOCK_NB.

Бписок ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ²

stream
РСсурс (resource) указатСля Ρ„Π°ΠΉΠ»ΠΎΠ²ΠΎΠΉ систСмы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ ΡΠΎΠ·Π΄Π°ΡŽΡ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ fopen().
operation

operation ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ значСния:

  • LOCK_SH для получСния раздСляСмой Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ (Ρ‡Ρ‚Π΅Π½ΠΈΠ΅).
  • LOCK_EX для получСния эксклюзивной Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ (запись).
  • LOCK_UN для снятия Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ (раздСляСмой ΠΈΠ»ΠΈ эксклюзивной).

Π€Π»Π°Π³ LOCK_NB Π΄ΠΎΠ±Π°Π²Π»ΡΡŽΡ‚ ΠΊΠ°ΠΊ Π±ΠΈΡ‚ΠΎΠ²ΡƒΡŽ маску ΠΊ ΠΎΠ΄Π½ΠΎΠΉ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ ΠΈΠ· списка Π²Ρ‹ΡˆΠ΅, Ссли функция flock() Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π²ΠΎ врСмя ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ Ρ„Π°ΠΉΠ»Π°.

would_block

ΠΠ΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Ρ‚Ρ€Π΅Ρ‚ΠΈΠΉ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ 1, Ссли Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° Π±ΡƒΠ΄Π΅Ρ‚ Π±Π»ΠΎΠΊΠΈΡ€ΡƒΡŽΡ‰Π΅ΠΉ (Π² ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ errno Π±ΡƒΠ΄Π΅Ρ‚ записан ΠΊΠΎΠ΄ ошибки EWOULDBLOCK).

Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹Π΅ значСния

Ѐункция Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ true, Ссли Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠ»Π°ΡΡŒ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ, ΠΈΠ»ΠΈ false, Ссли Π²ΠΎΠ·Π½ΠΈΠΊΠ»Π° ошибка.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #1 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ flock()

<?php

$fp
= fopen("/tmp/lock.txt", "r+");

if (
flock($fp, LOCK_EX)) { // ВыполняСм ΡΠΊΡΠΊΠ»ΡŽΠ·ΠΈΠ²Π½ΡƒΡŽ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ
ftruncate($fp, 0); // ΠžΡ‡ΠΈΡ‰Π°Π΅ΠΌ Ρ„Π°ΠΉΠ»
rewind($fp);
fwrite($fp, "ПишСм Ρ‡Ρ‚ΠΎ-Π½ΠΈΠ±ΡƒΠ΄ΡŒ сюда\n");
fflush($fp); // ΠžΡ‡ΠΈΡ‰Π°Π΅ΠΌ Π²Ρ‹Π²ΠΎΠ΄ ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΌΠ΅Π½ΠΎΠΉ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ
flock($fp, LOCK_UN); // Π‘Π½ΠΈΠΌΠ°Π΅ΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ
} else {
echo
"НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ!";
}

fclose($fp);

?>

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #2 ИспользованиС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ flock() с ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ LOCK_NB

<?php

$fp
= fopen('/tmp/lock.txt', 'r+');

/* Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ ΠΎΠΏΡ†ΠΈΡŽ LOCK_NB Π² ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΡŽ LOCK_EX */
if (!flock($fp, LOCK_EX | LOCK_NB)) {
echo
'НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ';
exit(-
1);
}

/* ... */

fclose($fp);
?>

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

Π’ систСмах Windows функция flock() Π²ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΡƒΡŽ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ вмСсто Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΉ. ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Π°Ρ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° Ρ‚Π°ΠΊΠΆΠ΅ поддСрТиваСтся Π² ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Ρ… систСмах Π½Π° Π±Π°Π·Π΅ Linux ΠΈ System V Ρ‡Π΅Ρ€Π΅Π· стандартный ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ систСмный Π²Ρ‹Π·ΠΎΠ² fcntl(): Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ, Ссли для Ρ„Π°ΠΉΠ»Π° установлСн Π±ΠΈΡ‚ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ setgid ΠΈ ΡΠ±Ρ€ΠΎΡˆΠ΅Π½ Π±ΠΈΡ‚ Π³Ρ€ΡƒΠΏΠΏΠΎΠ²ΠΎΠ³ΠΎ выполнСния. Π§Ρ‚ΠΎΠ±Ρ‹ схСма Ρ€Π°Π±ΠΎΡ‚Π°Π»Π° ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ Π² Linux, Ρ„Π°ΠΉΠ»ΠΎΠ²ΡƒΡŽ систСму Ρ‚Π°ΠΊΠΆΠ΅ Π½ΡƒΠΆΠ½ΠΎ ΡΠΌΠΎΠ½Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ с ΠΎΠΏΡ†ΠΈΠ΅ΠΉ mand.

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ функция flock() Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ ΡƒΠΊΠ°Π·Π°Ρ‚Π΅Π»ΡŒ Π½Π° Ρ„Π°ΠΉΠ», Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, придётся ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π·Π°Ρ‰ΠΈΡ‚ΠΈΡ‚ΡŒ доступ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π²Ρ‹ ΡΠΎΠ±ΠΈΡ€Π°Π΅Ρ‚Π΅ΡΡŒ ΠΎΠ±Ρ€Π΅Π·Π°Ρ‚ΡŒ, ΠΎΡ‚ΠΊΡ€Ρ‹Π² Π² Ρ€Π΅ΠΆΠΈΠΌΠ΅ записи (Π² Ρ€Π΅ΠΆΠΈΠΌΠ°Ρ… Β«wΒ» ΠΈΠ»ΠΈ Β«w+Β» Π² качСствС Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ fopen()).

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

Π€ΡƒΠ½ΠΊΡ†ΠΈΡŽ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΎ Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° дСскрипторах Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ функция fopen(), ΠΈΠ»ΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ²Ρ‹Ρ… дСскрипторах ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… ΠΏΠΎΡ‚ΠΎΠΊΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΡŽΡ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ streamWrapper::stream_lock().

Π’Π½ΠΈΠΌΠ°Π½ΠΈΠ΅

ΠŸΡ€ΠΈΡΠ²ΠΎΠ΅Π½ΠΈΠ΅ Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ значСния ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρƒ stream Π² ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΌ дальшС ΠΊΠΎΠ΄Π΅ снимСт Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ.

Π’Π½ΠΈΠΌΠ°Π½ΠΈΠ΅

Π’ рядС ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Ρ… систСм функция flock() Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ процСссов. ΠŸΡ€ΠΈ Ρ€Π°Π±ΠΎΡ‚Π΅ с ΠΌΠ½ΠΎΠ³ΠΎΠΏΠΎΡ‚ΠΎΡ‡Π½Ρ‹ΠΌΠΈ сСрвСрными API, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ISAPI, нСльзя ΠΏΠΎΠ»Π°Π³Π°Ρ‚ΡŒΡΡ Π½Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ flock(), Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π·Π°Ρ‰ΠΈΡ‚ΠΈΡ‚ΡŒ Ρ„Π°ΠΉΠ»Ρ‹ ΠΎΡ‚ Π΄Ρ€ΡƒΠ³ΠΈΡ… PHP-скриптов, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ Π² ΠΏΠ°Ρ€Π°Π»Π»Π΅Π»ΡŒΠ½ΠΎΠΌ ΠΏΠΎΡ‚ΠΎΠΊΠ΅ Π½Π° Ρ‚ΠΎΠΌ ΠΆΠ΅ сСрвСрС!

Π€ΡƒΠ½ΠΊΡ†ΠΈΡŽ flock() Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ старыС Ρ„Π°ΠΉΠ»ΠΎΠ²Ρ‹Π΅ систСмы Π½Π°ΠΏΠΎΠ΄ΠΎΠ±ΠΈΠ΅ FAT ΠΈ Π΅Ρ‘ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹Π΅, поэтому Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ Π±ΡƒΠ΄Π΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ false Π² этих окруТСниях.

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

Если Π² Windows процСсс Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΎΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ Ρ„Π°ΠΉΠ» Π²ΠΎ Π²Ρ‚ΠΎΡ€ΠΎΠΉ Ρ€Π°Π·, ΠΎΠ½ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ доступ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ Ρ‡Π΅Ρ€Π΅Π· Π²Ρ‚ΠΎΡ€ΠΎΠΉ дСскриптор, ΠΏΠΎΠΊΠ° Π½Π΅ Ρ€Π°Π·Π±Π»ΠΎΠΊΠΈΡ€ΡƒΠ΅Ρ‚ Ρ„Π°ΠΉΠ».

οΌ‹Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ 33 notes

up
185
Antti Haapala ΒΆ
18 years ago
The supplied documentation is vague, ambiguous and lacking, and the user comments contain erroneous information! The flock function follows the semantics of the Unix system call bearing the same name. Flock utilizes ADVISORY locking only; that is, other processes may ignore the lock completely; it only affects those that call the flock call.

LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock.

LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time.

If the file has been LOCKED with LOCK_SH in another process, flock with LOCK_SH will SUCCEED. flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED.

If the file has been locked with LOCK_EX in another process, the CALL WILL BLOCK UNTIL ALL OTHER LOCKS have been released.

If however, you call flock on a file on which you possess the lock, it will try to change it. So: flock(LOCK_EX) followed by flock(LOCK_SH) will get you a SHARED lock, not "read-write" lock.
up
6
ravenswd at gmail dot com ΒΆ
11 years ago
Note that Example #1 contains a bug: ftruncate() does *not* re-set the file pointer to the beginning of the file. You need to execute a call to rewind() afterward. I realize that the ftruncate page does mention this, but if anybody copies the example above (as I did), their program will not work correctly unless they fix this.
up
4
Antti Haapala ΒΆ
18 years ago
Further information on flock: The system is not restarted if a signal is delivered to the process, so flock will happily return false in case of SIGALRM, SIGFPE or something else.
up
15
jlh at gmx dot ch ΒΆ
9 years ago
When a file is closed the lock will be released by the system anyway, even if PHP doesn't do it explicitly anymore since 5.3.2 (the documentation is very confusing about this).

However, I had a situation on an apache/PHP server where an out-of-memory error in PHP caused file handles to not be closed and therefore the locks where kept even thought PHP execution had ended and the process had returned to apache to serve other requests. The lock was kept alive until apache recycled those processes.

This lack of proper clean up basically makes flock() completely unreliable.
up
13
forzi at mail333 dot com ΒΆ
11 years ago
Simple Helper for lock files creation

<?php

class FileLocker {
    protected static $loc_files = array();

    public static function lockFile($file_name, $wait = false) {
        $loc_file = fopen($file_name, 'c');
        if ( !$loc_file ) {
            throw new \Exception('Can\'t create lock file!');
        }
        if ( $wait ) {
            $lock = flock($loc_file, LOCK_EX);
        } else {
            $lock = flock($loc_file, LOCK_EX | LOCK_NB);
        }
        if ( $lock ) {
            self::$loc_files[$file_name] = $loc_file;
            fprintf($loc_file, "%s\n", getmypid());
            return $loc_file;
        } else if ( $wait ) {
            throw new \Exception('Can\'t lock file!');
        } else {
            return false;
        }
    }

    public static function unlockFile($file_name) {
        fclose(self::$loc_files[$file_name]);
        @unlink($file_name);
        unset(self::$loc_files[$file_name]);
    }

} 

if ( !FileLocker::lockFile('/tmp/1.lock') ) {
    echo "Can't lock file\n";
    die();
}
sleep(10);
FileLocker::unlockFile('/tmp/1.lock');
echo "All Ok\n";
up
14
webmaster at bitpush dot com ΒΆ
15 years ago
Regarding the change in PHP 5.3.2 with locked files:

Without having studied the PHP source code in detail, the situation appears to be as follows when the PHP function fclose() is called:

Before 5.3.2 PHP would check if the file was locked, then release the lock, and then close the file.

From 5.3.2 PHP just closes the file.

But note, that the operating system releases the lock automatically when the file is closed. Therefore a call to fclose() STILL releases the lock (this is tested with PHP 5.3.2, Linux, x64).
up
17
mdessaintes at gmail dot com ΒΆ
13 years ago
I just spent some time (again) to understand why a reading with file_get_contents() and file was returning me an empty string "" or array() whereas the file was existing and the contents not empty.

In fact, i was locking file when writing it (file_put_contents third arg) but not testing if file was locked when reading it (and the file was accessed a lot).

So, please pay attention that file_get_contents(), file() and maybe others php files functions are going to return empty data like if the contents of the file was an empty string.

To avoid this problem, you have to set a LOCK_SH on your file before reading it (and then waiting if locked).

Something like this :

<?php
public static function getContents($path, $waitIfLocked = true) {
    if(!file_exists($path)) {
        throw new Exception('File "'.$path.'" does not exists');
    }
    else {
        $fo = fopen($path, 'r');
        $locked = flock($fo, LOCK_SH, $waitIfLocked);
        
        if(!$locked) {
            return false;
        }
        else {
            $cts = file_get_contents($path);
            
            flock($fo, LOCK_UN);
            fclose($fo);
            
            return $cts;
        }
    }
}
?>

Code to test by yourself :

abc.txt :
someText

file.php :
<?php
$fo = fopen('abc.txt', 'r+');

flock($fo, LOCK_EX);
sleep(10);
flock($fo, LOCK_UN);
?>

file2.php : 
<?php
var_dump(file_get_contents('abc.txt'));
var_dump(file('abc.txt'));
?>

Then launch file.php and switch to file2.php during the 10 seconds and see the difference before/after
up
12
dejangex at yahoo dot com ΒΆ
16 years ago
Actually, there is no use of the while loop with the usleep. My testing has revealed the following:

<?php
//some code here
flock($file_handle, LOCK_EX) // <- Your code will pause here untill you get the lock for indefinite amount of time or till your script times out
//some code here
?>

This will actually check for the lock without pausing and then it will sleep:

<?php
//code here
while (!flock($file_handle, LOCK_EX | LOCK_NB)) {
 //Lock not acquired, try again in:
 usleep(round(rand(0, 100)*1000)) //0-100 miliseconds
}
//lock acquired
//rest of the code
?>

The problem is, if you have a busy site and a lots of locking, the while loop may not acquire the lock for some time. Locking without LOCK_NB is much more persistent and it will wait for the lock for as long as it takes. It is almose guaranteed that the file will be locked, unless the script times out or something.

Consider these two scripts: 1st one is ran, and the second one is ran 5 seconds after the first.

<?php
//1st script
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //lock the file
sleep(10); //sleep 10 seconds
fclose($file_handle); //close and unlock the file
?>

<?php
//2nd script
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //lock the file
fclose($file_handle); //close and unlock the file
?>

If you run 1st and then the 2nd script,the 2nd script will wait untill the 1st has finished. As soon as the first script finishes, the second one will acquire the lock and finish the execution. If you use flock($file_handle, LOCK_EX | LOCK_NB) in the 2nd script while the 1st script is running, it would finish execution immediately and you would not get the lock.
up
10
Fernando Gabrieli fgabrieli at gmail ΒΆ
17 years ago
When writing to a file, you should avoid using w+ because it would erase the contents of the file before locking

If you need to write the complete file again you could use the following instead:

<?php
$fp = fopen('yourfile.txt', 'a') ;

if (flock($fp, LOCK_EX)) {
    ftruncate($fp, 0) ; // <-- this will erase the contents such as 'w+'
    
    fputs($fp, 'test string') ;
    
    flock($fp, LOCK_UN) ;
}

fclose($fp) ;
?>

Best,
Fernando Gabrieli
up
8
metamarkers at gmail dot com ΒΆ
13 years ago
you can wrap a lock as an object to make it a scope-based lock. when the lock object is no longer referenced, like when it's unset or the owner returns, the destructor will call unlock.

this way you can just create a lock object and forget about it.

<?php
class lock {

    private $handle;

    public static function read ( $handle ) {
        $lock = new static();
        $lock->handle = $handle;
        return flock($handle,LOCK_SH) ? $lock : false;
    }

    public static function write ( $handle ) {
        $lock = new static();
        $lock->handle = $handle;
        return flock($handle,LOCK_EX) ? $lock : false;
    }

    public function __destruct ( ) {
        flock($this->handle,LOCK_UN);
    }

}
?>
up
4
markus at malkusch dot de ΒΆ
9 years ago
And here's the timeout template for UNIX:

<?php
pcntl_signal(SIGALRM, function() {});
pcntl_alarm(3);
try {
    if (!flock($handle, LOCK_EX)) {
        throw new \Exception("Timeout");
    }
} finally {
    pcntl_alarm(0);
    pcntl_signal_dispatch();
    pcntl_signal(SIGALRM, SIG_DFL);
}
?>
up
2
mallory dot dessaintes at gmail dot com ΒΆ
18 years ago
I have noticed that if you change the value of your fopen ressource, the lock is working no longer..

<?php

$fo = fopen('lockfile.txt','a');

flock($fo,LOCK_EX);

$fo = '';

// Lock is disable

?>
up
4
joel[at_sign]purerave.com ΒΆ
23 years ago
I have found that if you open a currently locked file with 'w' or 'w+' ("file pointer at the beginning of the file and truncate the file to zero length")  then it will not truncate the file when the lock is released and the file available.

Example I used to test it:
<?php
// a.php
$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // exclusive lock

$steps = 10;
// write to the file
for ($i=0; $i< $steps; $i++) {
    fwrite($fp, 'a '.time().' test '. $i."\n");
    sleep(1);
}
flock( $fp, LOCK_UN ); // release the lock
fclose( $fp );
?>

----------
<?php
// b.php

$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // exclusive lock

// ftruncate($fp, 0) is needed here! <----

$steps = 5;
// write to the file
for ($i=0; $i< $steps; $i++) {
    fwrite($fp, 'b '.time().' test '. $i."\n");
    sleep(1);
}
flock( $fp, LOCK_UN ); // release the lock
fclose( $fp );
?>

Loading a.php then loading b.php right after will result in:
b 1054075769 test 0
b 1054075770 test 1
b 1054075771 test 2
b 1054075772 test 3
b 1054075773 test 4
a 1054075764 test 5
a 1054075765 test 6
a 1054075766 test 7
a 1054075767 test 8
a 1054075768 test 9

As you can see, b.php does not truncate the file as the w+ would suggest if the file were instantly available. But only moves the pointer to the begining of the file. If b.php was loaded after a.php finished then there would be no "a ..." lines in the file, since it would be truncated.

To fix this you have to add ftruncate($fp, 0) right after the flock.

'r+' and 'a' seem to work fine, though.
up
2
marc dot vanwoerkom at fernuni-hagen dot de ΒΆ
20 years ago
I ran into a loop because I just checked for true (= you got the lock) as return value of flock() and tried again when I got a false.

<?php
    function naive_wait_for_file($fp) {
        while (true) {
            if (flock($fp, LOCK_EX)) {
                return;
            }
            $k = rand(0, 20);
            usleep(round($k * 10000));  # k * 10ms
        }
    }
?>

Unfortunately in one case the $fp I put in was invalid, so I always got false and got stuck.
Lesson: check if your $fp is valid before entering the loop, or look closer if you get a false.

<?php
    function wait_for_file($fp) {
        if ($fp === false) {
            return;
        }
        while (true) {
            if (flock($fp, LOCK_EX)) {
                return;
            }
            $k = rand(0, 20);
            usleep(round($k * 10000));  # k * 10ms
        }
    }
?>
up
1
Joby <god at NOSPAMPLEASE dot greentinted dot net> ΒΆ
21 years ago
I'm thinking that a good way to ensure that no data is lost would be to create a buffer directory that could store the instructions for what is to be written to a file, then whenever the file is decidedly unlocked, a single execution could loop through every file in that directory and apply the indicated changes to the file.

I'm working on writing this for a flat-file based database.  The way it works is, whenever a command is issued (addline, removeline, editline), the command is stored in a flat file stored in a folder named a shortened version of the filename to be edited and named by the time and a random number.  In that file is a standardized set of commands that define what is to be done to what file (the likes of "file: SecuraLog/index_uid" new line "editline: 14").

Each execution will check every folder in that directory for files and a certain amount of time (I don't know how long, maybe 1-2 seconds) is spent making pending changes to unlocked files.  This way no changes will be lost (i.e. person 1 makes a change at the same time as person 2, and person 1 loses the race by just enough to have their changed version of the file overwritten by person 2's version) and there will be no problems with opening an empty open file.
up
6
tinymountain at nospam dot gmail dot com ΒΆ
17 years ago
Here's a handy class to allow retrying a write with flock a set number of times. If it can't flock, it will sleep for a brief random interval and try again. If you have a lot of concurrent writes going on, you can use it to avoid data corruption.

<?php
class SafeWriter
{
    // suggested mode 'a' for writing to the end of the file
    public static function writeData($path, $mode, $data)
    {
        $fp = fopen($path, $mode);
        $retries = 0;
        $max_retries = 100;

        if (!$fp) {
            // failure
            return false;
        }

        // keep trying to get a lock as long as possible
        do {
            if ($retries > 0) {
                usleep(rand(1, 10000));
            }
            $retries += 1;
        } while (!flock($fp, LOCK_EX) and $retries <= $max_retries);

        // couldn't get the lock, give up
        if ($retries == $max_retries) {
            // failure
            return false;
        }

        // got the lock, write the data
        fwrite($fp, "$data\n");
        // release the lock
        flock($fp, LOCK_UN);
        fclose($fp);
        // success
        return true;
    }
}
?>
up
2
ondrej dot nemecek at gmail dot com ΒΆ
14 years ago
Notice: On NFS with NFS locking daemon you cannot open file for reading and than lock exklusively:

$rFopened  = fopen($sFile,'r');
$bResult   = flock($rFopened, LOCK_EX);

var_dump($bResult); // returns FALSE

Mixed fopen modes (a+, w+ ...) works with LOCK_EX fine.
up
5
John dot wellesz at teaser dot fr ΒΆ
18 years ago
I just want to add a note about making atomic lock on NFS, there is only two
ways:

- 1 (the most robust but the most complicate) - It's to use link() to create a
  hard link to a file you want to lock (on the same FS of course).
  (On most NFS implementations, Link() is atomic)

Once you created a hard link (not a symbolic link), with a unique randomly
generated name, call stat() on it and count the number of link (nlink), if there
is only 2 then the file is locked.

If there is more than two you have to unlink() the link you just created and
create a new one with a new unique name (else NFS will use its cache and stat
will return wrong data) then call stat() on the new link and test the number of
links again, repeat this operation until you get the lock.

You have to use usleep() between the link() attempts with a fixed + random
sleep value to avoid dead lock situations (link() and unlink() may be atomic
but not instantaneous)

Also note than when you unlink a file through NFS, if NFS think that the file
is still in use, it will create a .nfs link to this file until it realizes the
file is no longer in use... A wrong timing could generate thousands of those
files and a deadlock situation.  Because of this when a deadlock situation
occurs or if your stat() command returns a very high number of links, you have
to look for .nfs file in the same directory you created your links and unlink
all the .nfs file you find (sometimes NFS take its time to remove them)

- 2 (the simplest) - the second method is to use a lock server and lock daemons
  on each client that will forward lock request to the server... (this is more
dangerous than the first method because the daemons may be killed...)

Here is for reference the function I created to make atomic locks through NFS
(this function is in production since at least 4 years now), it's just for
reference because it uses many external functions to do its job but you can see
the principle:

http://pastey.net/85793
up
2
rudy dot metzger at pareto dot nl ΒΆ
21 years ago
Like a user already noted, most Linux kernels (at least the Redhat ones) will return false, even if you locked the file. This is because the lock is only ADVISORY (you can check that in /proc/locks). What you have to do there is to evalute the 3rd parameter of flock(), $eWouldBlock. See for an example below. Note however that if you
lock the file in non blocking mode, flock() will work as expected (and blocks the script).

<?php
                                                                                
$fp = fopen( "/var/lock/process.pid", "a" );
if ( !$fp || !flock($fp,LOCK_EX|LOCK_NB,$eWouldBlock) || $eWouldBlock ) {
  fputs( STDERR, "Failed to acquire lock!\n" );
  exit;
}
                                                                                
// do your work here
                                                                                
fclose( $fp );
unlink( "/var/lock/process.pid" );
                                                                                
?>
up
1
holdoffhunger at gmail dot com ΒΆ
15 years ago
I've been having trouble getting Flock to work when I read a file, delete it, and then output slightly changed information back to the same location.  When deleting with Unlink, there's a very brief period of time where no file exists.  But, if you do an fopen using the "w" mode, it keeps the file in existence, but deletes all of its data when you go to write to it.  That way, the file never actually disappears, and another script accessing the same file with flock won't get a "file doesn't exist" error.
up
1
admin ifyouwantblood de ΒΆ
18 years ago
besides from what the manual says about locking a file opendend in w or w+ and using a special lock file for these cases, you should simply truncate the file yourself with ftruncate() after writing:

<?php

$data='some data';
$handle=fopen('file','r+');
flock($handle,LOCK_EX);
fwrite($handle,$data);
ftruncate($handle,ftell($handle));
flock($handle,LOCK_UN);
fclose($handle);

?>

now the file will have the size of $data without opening the file in w mode but with a lock on the file.

to the previous writers jpriebe and mallory:
of course the lock is lost in this case, but thats simply because the file is closed by PHP. and closing the file means unlocking it (same as when you use fclose() yourself).
up
1
damasta at onwebworx dot net ΒΆ
20 years ago
just wanted to say that you will most likely fail if you use a separate lock file together with register_shutdown_function.

my script did some different actions... resizing pictures, rotating them and this stuff. it needed a "database" file to get the correct file locations. this database file also stored some flags. and of course the script had to save that file when it was done.

because of my script exited on many different points depending on the action i used register_shutdown_function to save the file. it wanted to use a locking system to be sure the script doesn't overwrite the data another process had written into it some microseconds before. i was running on windows 2000 and apache2 on my developing machine, and flock always returned true for some reason... so i used a separate lock file. the script looked for it at the beginning and exited if it was found. otherwise it created it. but this file had to be deleted at the end. i put the unlink command into the registered shutdown-function but it never deleted the file. i tried clearstatcache and some other stuff but it didn't help.

maybe this helps someone.
up
1
emilchemical at gmail dot com ΒΆ
10 years ago
If you are interested in using file locking as indicator that your console app is running and you don't want to start it again:

 if (!flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
                if ($wouldblock) {
                    //Another process holds the lock!
               
                } else {
                    //Couldn't lock for another reason, e.g. no such file
             
                }
            } else {
                //Lock obtained

                startJob();
            }
Please note that this is platform independent solution, but you have restrictions in PHP version (5.5.22 on Windows).
Also, some old file systems are not supported.
up
3
Evan Battaglia ΒΆ
15 years ago
LOCK_NB seems to be checked and works fine in Windows, too, in PHP 5.3.3.

For instance, try concurrently running two instances of the following script (via the CLI). The second prints "Didn't quite get the lock..." as expected, whereas w/o the LOCK_NB flag, it just hangs.

<?php
$x = fopen("flocktest.txt", "w");
if (flock($x, LOCK_EX|LOCK_NB)) {
    print "No problems, I got the lock, now I'm going to sit on it.";
    while (true)
        sleep(5);
} else {
    print "Didn't quite get the lock. Quitting now. Good night.";
}
fclose($x);
?>
up
1
sinus-php at sinpi dot net ΒΆ
11 years ago
"Assigning another value to handle argument in subsequent code will release the lock."
Note: this is also true for losing the handle's context completely (like returning from a function, in which the handle was a local variable).

<?php
touch("testfile");

function mylock() {
  $F1 = fopen("testfile","r");
  if (flock($F1,LOCK_EX|LOCK_NB)) echo "First lock OK\n"; else echo "First lock FAIL\n";
  $F2 = fopen("testfile","r");
  if (flock($F2,LOCK_EX|LOCK_NB)) echo "Second lock OK\n"; else echo "Second lock FAIL\n";
}

mylock();
echo "Function returned.\n";
mylock();

unlink("testfile");
?>

Prints out:

First lock OK
Second lock FAIL
Function returned.
First lock OK
Second lock FAIL

This will lock the testfile, then attempt to lock it again with a new file handle (obviously failing). Trying this again, though, results in proper locking again (and then failing again), because when exiting the function both handles are lost and automatically unlocked.
up
1
jerry at gh33 dot org ΒΆ
19 years ago
Indeed, flock() will not work reliably when the underlying filesystem is NFS. The proper way to perform file locking, in this case, would be to use PHP's link() function. From the Linux man page of open():

       O_EXCL When used with O_CREAT, if the file  already  exists  it  is  an
              error  and  the open will fail. In this context, a symbolic link
              exists, regardless of where its points to.  O_EXCL is broken  on
              NFS file systems, programs which rely on it for performing lock-
              ing tasks will contain a race condition.  The solution for  per-
              forming  atomic  file  locking  using  a lockfile is to create a
              unique file on the same fs  (e.g.,  incorporating  hostname  and
              pid),  use  link(2)  to  make  a link to the lockfile. If link()
              returns 0, the lock is successful.  Otherwise,  use  stat(2)  on
              the  unique  file to check if its link count has increased to 2,
              in which case the lock is also successful.
up
2
andy at andyslife dot org ΒΆ
2 years ago
Important if using this to lock a class into a single instance (long duration processes). The file handle needs to be a class wide variable like $this->singleinstance. Not local to the __construct function like $localvariable. The reason is the local variable is gone after __construct is called so the lock lasts for only millisconds and not for the full duration of class running in memory.
up
2
orkans at gmail dot com ΒΆ
2 years ago
Watch out for implied lock release!

<?php
flock($fp, LOCK_SH);

if(!flock($fp, LOCK_EX | LOCK_NB)) {
    echo 'Unable to obtain exclusive lock';
}

// Now the LOCK_SH is gone too!

?>
up
0
php at richardneill dot org ΒΆ
1 month ago
Sometimes, for tidiness-sake, it's tempting to lock either the file you are editing, or the directory its in. This will (sometimes) work. But save yourself a lot of pain, and use a dedicated lock file!
up
2
peripeltus at gmail dot com ΒΆ
9 years ago
Relying on flock for synchronise things is risky, consider using extensions like SYNC or pthreads instead.
up
0
tom dot vom dot berg at online dot de ΒΆ
12 years ago
if you are used to reply with qualified error messages by using "track_errors = 1" and $php_errormsg,  flock() will nark you, if you use LOCK_NB .

If flock() fails due to LOCK_NB $php_errormsg will not be created and filled with an error message like 'could not lock file, file already locked'.

You can use some little workaround which does also on WIN:

#---------------------------------
GLOBAL $MYERRORMSG;

function some_file_operations($filename)
{
    GLOBAL $MYERRORMSG;

    # ...
    # ...

    if (! @flock($fh, LOCK_SH | LOCK_NB, $wb))
       {
        if (!isset($php_errormsg)) $php_errormsg = 'could not lock file, file already locked';
        $MYERRORMSG = $php_errormsg;
        return false;
    }    
}
#--------------------------------

In case of fail $MYERRORMSG now will hold a qualified error message
up
2
candide at idees-et-solutions dot fr ΒΆ
18 years ago
Just a comment about the last method to lock files using filemtime().
What if   filemtime($fp[1]) == $fp[3]   because somebody modified the file less than 1s after the value of $fp[3] was picked up?
Then this modification will be lost...? 

This system to lock files is made to prevent problems when two modifications are so close that they can interfere, so the case "less than 1s" will often happen?

However, lose some modifications is better than spoil all the file...
up
0
Kuzma dot Deretuke at gmail dot com ΒΆ
16 years ago
I use exclusive writing to replace standard flock():

<?php
// get/set lock file name
function m_lock_file( $format = null ) {
    static $file_format = './%s.lock';
    
    if ($format !== null) {
        $file_format = $format;
    }
    
    return $file_format;
}

// acquire/check/release lock
function m_lock( $lockId, $acquire = null ) {
    static $handlers = array();
    
    if (is_bool($acquire)) {
        $file = sprintf(m_lock_file(), md5($lockId), $lockId);
    }
    
    if ($acquire === false) {
        if (isset($handlers[$lockId])) {
            @fclose($handlers[$lockId]);
            @unlink($file);
            unset($handlers[$lockId]);
        } else {
            trigger_error("Lock '$lockId' is already unlocked", E_USER_WARNING);
        }
    }
    
    if ($acquire === true) {
        if (!isset($handlers[$lockId])) {
            $handler = false;
            $count = 100;
            do {
                if (!file_exists($file) || @unlink($file)) {
                    $handler = @fopen($file, "x");
                }
                if (false === $handler) {
                    usleep(10000);
                } else {
                    $handlers[$lockId] = $handler;
                }
            } while (false === $handler && $count-- > 0);
        } else {
            trigger_error("Lock '$lockId' is already locked", E_USER_WARNING);
        }
    }
    
    return isset($handlers[$lockId]);
}
?>

Usage sample:

<?php
$lockId = "qq";

m_lock($lockId, true);
if (m_lock($lockId)) {
    echo "locked";

    // here you can perform any thread-safe operations
    usleep(300 * 1000);

    m_lock($lockId, false);
} else {
    echo "not locked";
}

?>