Номер телефона

Последнее обновление:

О коварстве функции flock() языка PHP

Эта функция предназначена для блокирования файлов, открытых для чтения и/или для записи. Это бывает необходимо в случаях, когда доступ к этим файлам имеет не один, а несколько процессов, желающих что-то прочитать оттуда или что-то записать. Если в случае чтения все проще – ведь содержимое файла не меняется - то с записью могут возникнуть проблемы. В самом деле, предположим, у Вас на сервере есть файл, в который записывается та или иная информация в результате запроса пользователя (из браузера). Нередко бывает так, что к одному и тому же файлу одновременно имеют доступ несколько пользователей. А все они делают такие запросы, что в результате в файл будет (точнее, должна) записываться информация ото всех их. И – одновременно?...

Как быть в этом случае?

Понятно, что для обработки каждого запроса (т.е. для каждого пользователя, по сути) запускается интерпретатор РНР – в виде еще одного процесса на сервере. Т.е. сколько пользователей взаимодействуют с файлом – столько процессов и будет запущено. В таких ситуациях первой делается та запись, запрос на которую пришел первым.

Точнее, делается попытка записи. А вот удастся ли она – это уже совсем другой вопрос.

Так вот, делается попытка записи. Но, ведь эта операция не может быть выполнена атомарно, т.е. без прерывания. Ведь у сервера – и иной работы много. Есть системные службы, а также, как уже говорилось, другие пользователи. И всем этим сервер занимается постоянно – по мере необходимости.

И вот, предположим, часть информации от запроса очередного пользователя была записана в файл. Указатель (позиции) в файле, соответственно, на том месте, на котором кончилась запись. И интерпретатор РНР готов бы продолжить запись, но операционная система сервера рассуждает иначе: ей ведь надо выполнить еще и массу другой работы. Поэтому она прерывает работу интерпретатора РНР (процесса, запущенного под управлением этого пользователя) и переходит к выполнению следующего задания. А таким заданием вполне может быть и обработка запроса еще одного пользователя, в соответствии с чем необходимо сделать запись, увы, в тот же самый файл.

Хорошо, началась запись. Например, с начала файла. Ведь этот второй процесс (интерпретатор РНР, запущенный для другого пользователя) понятия не имеет, что до него только что шла запись в тот же самый файл для перового пользователя.

Итак, начиная с начала файла, идет запись в тот же самый файл, но в рамках обработки запроса ДРУГОГО пользователя. Соответственно, во-первых, этот файл будет изменен. Во-вторых, после прерывания выполнения этого задания указатель в файле будет установлен уже где-то в другом месте…

И вот, интерпретатор РНР, недавно обрабатывавший запрос первого пользователя, вновь получил квант времени от операционной системы и приступил к продолжению записи…  НО: позиция указателя-то в файле – уже иная, так как она изменилась в результате записи от второго (другого) пользователя. Это, естественно, приведет к тому, что интерпретатор РНР, как ни в чем ни бывало, продолжит запись, думая, что он продолжает ее, стартуя не с той позиции, на которой он остановился ранее, а совсем с другой позиции… К чему это приведет, наверное, объяснять не надо: Т.е. информация будет записана интерпретатором РНР в файл, скорее всего, кусками и с разных мест (позиций). И точно так же будет дело обстоять и у других процессов - интерпретаторов РНР, которые ведут запись в этот же файл одновременно с ним (первым процессом – интерпретатором).

Так вот, чтобы не допускать подобной ситуации, во многих языках программирования имеется функция блокировки файла. В языке PHP она звучит так:

flock(…);

Язык дает две возможности для блокировки:

  • LOCK_SH для получения разделяемой блокировки (чтение),
  • LOCK_EX для получения эксклюзивной блокировки (запись).

Подразумевается, что эксклюзивная (исключительная) блокировка надежно блокирует файл. Если, мол, она сделана тем или иным процессом (который успел ее сделать), то писать в файл не смогут другие процессы до тех пор, пока эта блокировка не будет снята. Сделать это можно либо специальной командой, либо дождаться завершения работы процесса – интерпретатора PHP, обрабатывающего запрос того соответствующего пользователя (в последнем случае блокировка будет снята сборщиком мусора).

Таким образом, до тех пор, пока файл заблокирован, никакой другой процесс не сможет получить к нему доступ (для записи), даже в случае, если текущий интерпретатор РНР будет временно приостановлен (прерван). Соответственно, после возобновления работы запись в файл будет производиться именно с той позиции, на которой она окончилась до прерывания.

Т.е. в теории и в самом деле очень все удобно и красиво. Но… лишь в теории.

На самом деле – теория не всегда совпадает с практикой

Как минимум, потому, что в некоторых операционных системах управление блокировками файлом осуществляется на уровне… процессов. А это означает, что даже если кто-то (например, интерпретатор РНР, запущенный для обработки запроса первого пользователя) ЭКСЛЮЗИВНО заблокирует файл, это совсем не помешает записи в этот файл со стороны других процессов, например, интерпретаторов PHP, запущенных под управлением других пользователей.

Более того, даже если блокировка файлов осуществляется не на уровне процессов, а на уровне ядра операционной системы, то и там возможны крайне непредсказуемые результаты. Рассмотрим такой простой пример:

<?php

// Файл test.php

$f = fopen('1.txt', 'r');

flock($f, LOCK_EX);

file_put_contents('1.txt', 'Hello');

flock($f, LOCK_UN);


Также создадим файл с именем 1.txt, запишем туда что-нибудь, например, символы qwerty. И сохраним его.

Как видно, приведенный выше программный код открывает файл, затем исключительно (эксклюзивно) блокирует его. А дальше – функция file_put_contents() пытается записать в этот файл слово ‘Hello’.

Вроде бы, судя по всему, запись в тот файл НЕ ДОЛЖНА бы производиться, пока он не разблокирован, не так ли?

Но, на практике дело обстоит несколько, скажем так, своеобразным образом. Т.е.запись-то не производится… и даже возникает предупреждение, что-то типа:

Warning: file_put_contents() [function.file-put-contents]: Only 0 of 5 bytes written, possibly out of free disk space in Z:\home\site.ru\www\***\test.php on line 4

Таким образом, функция вначале ОЧИЩАЕТ(!) файл. А затем мило сообщает, что, мол, всего 0 байтов-то записано. Ну, раз файл-то заблокирован.

Т.е. как видно, и в самом деле делается запись в размере НОЛЬ символов – в файл. В итоге он, как и положено, становится НУЛЕВОЙ длины. Т.е. усекается. Соответственно, все то, что в нем было до этого момента, теряется. И восстановлению не подлежит.

Кстати, если вместо исключительной блокировки применить LOCK_SH, ничего не поменяется, файл все равно будет усекаться.

Но, это еще ладно бы. А дальше рассмотрим еще более тяжелый случай.

А что, если в функцию file_put_contents() добавить флаг FILE_APPEND?

Этот флаг означает, что функция не перепишет файл заново, а допишет к нему с конца, т.е. добавит новую информацию к уже имеющейся. Так вот, в таком случае НЕ ВОЗНИКНЕТ даже предупреждения. И функция file_put_contents() «честно» сделает запись, совершенно игнорируя сделанную только что блокировку…

Так что вот так. Это похоже на ситуацию из то ли сказки, то ли анекдота, когда одному дурачку, пришедшему на кухню и увидевшему упавшие на полу пирожки, сказали: « ты аккуратнее с пирожками на полу». В итоге, этот дурачок АККУРАТНО шел по кухне, АККУРАТНО наступая на каждый пирожок.

Это следует иметь в виду при разработке мало-мальски нагруженных приложений и сервисов, посещаемость которых достаточно высока и где могут возникнуть конкурентные процессы записи в один и тот же файл. Как видно, на функцию flock() в языке РНР надеяться НЕЛЬЗЯ.

Т.е. если в Вашем проекте "вдруг" и "почему-то" начало исчезать содержимое некоторых файлов - вспомните про эту нашу статью.

Примечание

Тестирование указанного программного кода проводилось в виртуальном сервере Denwer, PHP5.3, операционная система Windows 7. Вполне возможно, что в более поздних версиях языка РНР эта досадная оплошность устранена тем или иным образом.


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2024 Научный консалтинг