piątek, 30 maja 2008

Blokada plików w PHP

Dość często zdarza się, że trzymamy jakieś dane w pliku. Np. wiadomości z shoutboxa lub listę osób które podpisały petycje. Ważną rzeczą jest aby podczas zapisu zakładane były blokady dostępu. Sprawa jest dość oczywista - nie możemy dopuścić aby w jednym momencie dwa działające skrypty modyfikowały jeden plik, prowadzi to do oczywistej utraty danych. Aby temu zapobiegać należy zakładać na plik blokady podczas operacji zapisu.
W PHP dostępne są następujące poziomy blokad:
LOCK_SH = 1 - gdy odczytujemy z pliku
LOCK_EX = 2 - gdy zapisujemy do pliku
LOCK_UN = 3 - zdjęcie blokady
Jeśli chcemy założyć blokadę na zapis to wcześniej wszystkie inne blokady muszą być zdjęte. Aby założyć blokadę na odczyt wystarczy aby nie była na nim założona blokada na zapis.
Blokady zakłada się za pomocą funkcji flock posiadającej dwa parametry:
$handle - uchwyt na plik
$operation - typ blokady

Przykład:
$file = fopen('example.txt' 'r+');
flock($file, LOCK_EX);
fwrite($file, $example_data);
flock($file, LOCK_UN);
fclose($file)

taki kod gwarantuje nam poprawny zapis do pliku. W przypadku gdy skrypt zażąda dostępu do pliku, który jest aktualnie zablokowany to skrypt jest wstrzymywany do czasu zwolnienia blokady i uzyskania dostępu do pliku.

Istnieje także możliwość sprawdzenia czy plik jest zablokowany. Aby tego dokonać należy dodać 4 do kodu blokady którą sprawdzamy. Wywołanie flock z takim parametrem jako operacja zwróci false jest plik jest pod działaniem blokady lub true jeśli nie jest.
Np.
$file1 = fopen("somefile.txt", "r+");
$file2 = fopen("somefile.txt", "r+");

flock($file1, LOCK_EX);

if( flock($file2, LOCK_EX + 4) )
{
echo("File is not locked");
}else{
echo("File is locked");
}

flock($file1, LOCK_UN);

Wykonanie skryptu spowoduje wyświetlenie komunikatu "File is locked".

Ważna uwaga - NIE łaczymy blokad w taki sposób:
flock($file, LOCK_EX and LOCK_SH); // ŹLE !!
flock($file, LOCK_EX or LOCK_SH); // ŹLE !!
flock($file, LOCK_EX + LOCK_SH); // ŹLE !!

Blokady zakładamy jeden po drugim, NIGDY ich nie łączymy! Tak jest poprawnie:
flock($file, LOCK_EX); 
flock($file, LOCK_SH); // dobrze

Jeszcze dwie uwagi na koniec:
- sprawdzanie czy na plik została założona blokada nie działa pod Windowsem (;/).
- blokady nie działają na systemach plików NFS, FAT.

Wiedza może rzadko kiedy przydatna, ale z pewnością bezcenna podczas pisania biblioteki do parsowania własnego formatu pliku ;-)
Zapraszam też do poczytania komentarzy na stronie dokumentacji flock gdzie znajdziecie wiele przydatnych rozwiązań związanych z tą funkcją.

4 komentarze:

  1. Warto byloby nadmienic, ze funkcja file_put_contents() [PHP >= 5.1.0] ma takze w prototypie mozliwosc przekazywania flag - i jako flage mozna przekazac LOCK_EX(clusive). Przydaje sie to jako operacja atomiczna, i mozna oczekiwac/testowac w petli + random usleep() (FALSE dla bledu, int wielkosc zapisania jesli sie udalo).
    Mozna tez dodac FILE_APPEND lub ich kombinacje, ale to juz inna bajka.

    LOCK_EX przydaje sie wyjatkowo w sytuacjach cyklicznego zapisu do pliku (np. zapisywanie pobieranych z socketa danych).

    OdpowiedzUsuń
  2. po sesji postaram się coś dopisać ;)
    dziękuję za sugestie!

    OdpowiedzUsuń
  3. Świetny post. Dzięki za niego. Przeszukiwałem Google w poszukiwaniu informacji jak sprawdzi czy na pliku jest założony flock przez inny proces. Dopiero tutaj znalazłem taką informację.

    Ps: można sie dowiedzieć jak/gdzie znalazłeś informacje o takim rozwiązaniu:
    flock($file2, LOCK_EX + 4)

    OdpowiedzUsuń
  4. ojej :) Ja nawet juz nie pamietam, ze pisalem tego posta :P Ale dzieki, ciesze sie, ze moglem pomoc ;)

    OdpowiedzUsuń