Nette SafeStream
Nette SafeStream gwarantuje, że każda operacja odczytu i zapisu pliku odbywa się w izolacji. Oznacza to, że żaden wątek nie zacznie czytać pliku, który nie został jeszcze w pełni zapisany, ani wiele wątków nie nadpisze tego samego pliku.
Instalacja:
composer require nette/safe-stream
Do czego to służy?
Do czego właściwie służą operacje izolowane? Zacznijmy od prostego przykładu, który wielokrotnie zapisuje do pliku, a potem odczytuje z niego ten sam ciąg:
$s = str_repeat('Long String', 10000);
$counter = 1000;
while ($counter--) {
file_put_contents('file', $s); // zapisujemy
$readed = file_get_contents('file'); // odczytujemy
if ($s !== $readed) { // sprawdzamy
echo 'strings are different!';
}
}
Mogłoby się wydawać, że wywołanie echo 'strings are different!' nigdy nie może nastąpić. Prawda jest
odwrotna. Spróbuj uruchomić ten skrypt jednocześnie w dwóch kartach przeglądarki. Błąd wystąpi niemal natychmiast.
Jedna z kart odczyta plik w momencie, gdy druga nie skończyła jeszcze zapisywać go w całości, więc treść będzie niekompletna.
Kod nie jest więc bezpieczny, jeśli wykonywany jest wielokrotnie równolegle (czyli w wielu wątkach). W internecie nie jest to rzadkość, bo serwery często odpowiadają jednocześnie dużej liczbie użytkowników. Zapewnienie, że Twoja aplikacja działa niezawodnie także przy wykonywaniu w wielu wątkach (thread-safe), jest kluczowe. Inaczej może dojść do utraty danych i trudnych do wykrycia błędów.
Jak jednak widzisz, natywne funkcje PHP do odczytu i zapisu plików nie są izolowane ani atomowe.
Jak używać SafeStream?
SafeStream tworzy bezpieczny protokół, przez który można w izolacji odczytywać i zapisywać pliki standardowymi funkcjami
PHP. Wystarczy poprzedzić nazwę pliku przedrostkiem nette.safe://:
file_put_contents('nette.safe://file', $s);
$s = file_get_contents('nette.safe://file');
SafeStream zapewnia, że w danym momencie do pliku może pisać najwyżej jeden wątek. Pozostałe wątki czekają w kolejce. Jeśli żaden wątek nie pisze, plik może równolegle czytać dowolna liczba wątków.
Z protokołem można używać wszystkich powszechnych funkcji PHP, na przykład:
// 'r' oznacza otwarcie tylko do odczytu
$handle = fopen('nette.safe://file.txt', 'r');
$ini = parse_ini_file('nette.safe://config.ini');
Ograniczenia
SafeStream izoluje odczyt i zapis zawartości plików, ale nie potrafi uczynić atomową każdej operacji. Miej te granice na uwadze:
- Informacje o pliku nie są izolowane. Funkcje, które tylko odpytują metadane, jak
file_exists(),filesize()czyis_file(), nie uczestniczą w blokowaniu. Mogą zwrócić informacje o pliku, który inny wątek właśnie zapisuje. - Usuwanie otwartego pliku w Windows. W przeciwieństwie do Uniksa Windows nie pozwala usunąć pliku, który inny wątek
ma właśnie otwarty, więc
unlink('nette.safe://file')może zawieść. - Automatyczne wycofanie niedokończonych zapisów. Jeśli zapis zawiedzie w połowie (na przykład zapełni się dysk), SafeStream przy zamykaniu pliku przycina go z powrotem do rozmiaru, jaki miał przed rozpoczęciem zapisu, więc nigdy nie zostawia częściowo zapisanych danych.
Jeśli aktualizujesz do nowszej wersji, zajrzyj na stronę aktualizacji.