Проясните пожалуйста с flock

Крот

Новичок
Проясните пожалуйста с flock

Провел эксперимент...

1. Создал файл a.php вот такого содержания
PHP:
<?php

$file = "abc.txt";

$fp = fopen($file, "w");
flock($fp, LOCK_EX);
sleep(20);
flock($fp, LOCK_UN);

$file = "abc.txt";

$fp = fopen($file, "w");
fwrite($fp, "test");
fclose($fp);

echo file_get_contents($file);

?>

Запускаю a.php и пока он спит запискаю b.php
По-идее, b.php не может записать что-то в файл, т.к. он заблокирован (LOCK_EX); он ждет свой очереди, т.е. у b.php ничего не получится до тех пор, пока a.php не поспит 20 секунд и не отключит лок. На деле получается совсем другое...

Проясните пожалуйста ситуацию с локами, а то запутался в том, как они работают.

Спасибо.
 

MiksIr

miksir@home:~$
Локи ничего не блокируют. Локи - это флажки, проверка которых лежит опять же на вас самих. Т.е. во втором файле так же обязательно использовать flock
 

Духовность™

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

Сравни результаты:

a.php - запускается первым
PHP:
<?
error_reporting(E_ALL);
$file = "abc.txt";

$fp = fopen($file, "a");
flock($fp, LOCK_EX);
sleep(10);
fwrite($fp, 'aaaaaaa');
flock($fp, LOCK_UN);
fclose($fp);
?>
b.php - запускается вторым
PHP:
<?
error_reporting(E_ALL);
$file = "abc.txt";

$fp = fopen($file, "a");
flock($fp, LOCK_EX);
fwrite($fp, "bbbbbb");
flock($fp, LOCK_UN);
fclose($fp);
?>
Если мы запустим эти два скрипта, то b.php будет ЖДАТЬ пока не снимется с файла блокировка файла a.php. Когда это случится, т.е. a.php запишет в файл, только после этого в файл запишет b.php. Итого в файл данные запишутся ПРАВИЛЬНО: сначала строка 'aaa', потом строка 'bbb'.

Теперь если убрать блокировку из b.php
PHP:
<?
error_reporting(E_ALL);
$file = "abc.txt";

$fp = fopen($file, "a");
//flock($fp, LOCK_EX);
fwrite($fp, "bbbbbb");
//flock($fp, LOCK_UN);
fclose($fp);
?>
то результат будет "непредсказуемым" - запишется только строка 'ааа', при попытке записи скриптом b.php запись в файл не была произведена.
 

Крот

Новичок
Спасибо, разжевали!

А могу я как-нибудь просто проверить залочен в данный момент файл или нет. Просто в моём контексте нужно не дожидаться снятия и писать в файл, а немного по-другому, а имеено...

1. Сперва я открываю файл, который содержит данные. Лочу его, __при__этом__не__стирая__данные. И начинаю получать обновленные данные. Как только новые данные получены - я обрезаю файл до нулевой длины и записываю их туда.

2. Пока файл находится в состоянии лока (вот как бы это проверить только?) - я только читаю из него (уже как бы слегка устаревшие данные).


Этот же механизм можно реализовать с помощью 2х файлов, один из которых будет периодически обнуляться, указывая процессам на то - из какого файла сейчас читать.

Но хочется попробовать сделать через локи.
 

cDLEON

Онанист РНРСlub
а почему бы сначала не получить "обновлённые данные", а потом их записать?
 

Макс

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

Вообще лучше расскажи какую ты задачу решаешь. Может тебе и не нужны здесь флоки
 

Духовность™

Продвинутый новичок
Крот
правило такое: если ты лочишь файл, то любые другие действия с ним должны так же сопровождаться локом. Тогда будет польза от блокировки.
 

Крот

Новичок
Автор оригинала: Макс
В такой схеме существует вероятность, что скрипт проверил что файл залочен, и стал из него читать. В этот момент, процесс который залочил, обрезает файл. В итоге читающий процесс может получить только порцию данных - то есть данные могут быть битыми.

Вообще лучше расскажи какую ты задачу решаешь. Может тебе и не нужны здесь флоки
Мне это нужно для того, чтобы немного усовершенствовать кэширование в Smarty...
Я проблему вот тут описал http://phpclub.ru/talk/showthread.php?s=&threadid=116601&highlight=%CA%FD%F8%E8%F0%EE%E2%E0%ED%E8%E5+Smarty

-~{}~ 31.10.09 10:12:

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

Макс

Старожил PHPClub
ну мемкеш для такой задачи - это ИМХО уже перебор
ИМХО нужно 2 файла:
- в одном хранится кеш смарти, который и будут читать все пхп-процессы
- второй файл - временный, он создается одним из процессов если нет первого кеш-файла. flock(), запись данных в него, закрываешь файл, rename($smarty_cache_tmp_file, $smarty_cache_file);
Из второго файла никто ничего не читает - в него лишь пишет один процес и потом делает rename().
 

Крот

Новичок
Автор оригинала: Макс
ну мемкеш для такой задачи - это ИМХО уже перебор
ИМХО нужно 2 файла:
- в одном хранится кеш смарти, который и будут читать все пхп-процессы
- второй файл - временный, он создается одним из процессов если нет первого кеш-файла. flock(), запись данных в него, закрываешь файл, rename($smarty_cache_tmp_file, $smarty_cache_file);
Из второго файла никто ничего не читает - в него лишь пишет один процес и потом делает rename().
Написал cache_handler для Smarty, который работает по такому принципу, как ты описал.
Работает шикарно, траф держит, пока нареканий никаких не было. Тьфу х 3!
 

godson

Новичок
Добрый день. У меня вопрос такой же: как безболезненно проверить - залочен ли файл? ... только для другой цели. Ответа я не нашел в сети, надеюсь на вашу подсказку...

Я хочу использовать flock для того чтобы реализовать постоянный рабочий процесс php-jabber-бота. Создаю лок-файл, и прописываю в скрипте бота вот что:

<?php
$w=fopen("lock.txt","a+"));
flock($w,LOCK_EX);
fwrite($w,"Запущен ".date("Y-m-d H:i:s", time())."\n");

******** тело скрипта ************

flock($w,LOCK_UN);
fclose($w);
?>

Создаю index.php который будет кроном стартоваться каждую минуту для проверки работоспособности бота и в случае "освобожения" лок-файла стартовать бота:

<?php
if ( $fp=fopen('/var/www/vhosts/******/httpdocs/bot/lock.txt',"r+") ) {
if (flock($fp, LOCK_EX)) {
print "Запускаем бота...";
$url='http://jabber.******.ru/bot/cool.php';
$a=file_get_contents($url);
flock($fp, LOCK_UN);
} else print "Бот в работе...";
} else print "Бот в работе...";
fclose($fp);
?>

В общем-то система работает, однако проблема в том, что при попатке открытия залоченного файла fopen происходит "зависание", или "застревание" процесса на длительный период. А ведь его нужно "пинать" каждую минуту... Вопрос в том - как можно "безболезненно" и быстро проверить - залочен ли файл, и при его локе просто "умирать"?
 

stillwaiting

Новичок
Автор оригинала: godson
Добрый день. У меня вопрос такой же: как безболезненно проверить - залочен ли файл? ... только для другой цели. Ответа я не нашел в сети, надеюсь на вашу подсказку...
Я бы сделал так: создал бы свою функцию mylock($f), myunlock($f) и islocked($f).

mylock($f) - лочит файл и создает в sys_get_temp_dir() файл "mysitename_lock15" с текущим time();
myunlock($f) - разлочивает файл и удаляет этот файл;
ismylocked() - пытается прочитать содержимое файла "mysitename_lock15" и, если разница времени, которое он хранит, с текущим time() меньше TIMEOUT значения, то возвращает true, иначе false;

Также можно ввести функцию mylocktouch(), которая обновляет значение в файле... это на случай если скрипт лочит файл надолго.
 

godson

Новичок
Автор оригинала: stillwaiting
Я бы сделал так: создал бы свою функцию mylock($f), myunlock($f) и islocked($f)....
Вообще, за это время я уже нашел выход. Но всё-равно спасибо за совет. Откопал в сети вот такое решение:

PHP:
$lockfile = fopen("lock.txt", "a+");  
if (!$lockfile || !flock($lockfile, LOCK_EX | LOCK_NB)) die("Бот уже запущен…");
Правда и с ним не обошлось без проблем: если запускать cron посредством php, то он каждую минуту по запуску создает кучу незакрытых процессов, и в конце концов вешает сервер. Я просто в кроне изменил задание, чтобы он запускал бота по URL путем wget... думаю, что можно так же запускать путем curl. В общем теперь меня всё устраивает, работает стабильно и безотказно.
 

stillwaiting

Новичок
Автор оригинала: godson
Вообще, за это время я уже нашел выход. Но всё-равно спасибо за совет. Откопал в сети вот такое решение:

PHP:
$lockfile = fopen("lock.txt", "a+");  
if (!$lockfile || !flock($lockfile, LOCK_EX | LOCK_NB)) die("Бот уже запущен…");
Клево, не знал про LOCK_NB, спасибо :)
 
Сверху