Nette SafeStream
Nette SafeStream garantuje, že každé čtení a zápis do souboru proběhne izolovaně. To znamená, že žádné vlákno nezačne číst soubor, který ještě není celý zapsán, nebo více vláken nebude přepisovat tentýž soubor.
Instalace:
composer require nette/safe-stream
K čemu je to dobré?
K čemu jsou izolované operace vlastně dobré? Začněme jednoduchým příkladem, který opakovaně zapisuje do souboru a následně z něj čte tentýž řetězec:
$s = str_repeat('Long String', 10000);
$counter = 1000;
while ($counter--) {
file_put_contents('soubor', $s); // write it
$readed = file_get_contents('soubor'); // read it
if ($s !== $readed) { // check it
echo 'řetězce se liší!';
}
}
Může se zdát, že volání echo 'řetězce se liší!' nikdy nemůže nastat. Opak je pravdou. Schválně si
zkuste tento skript spustit ve dvou záložkách prohlížeče zároveň. Chyba se dostaví prakticky okamžitě.
Jedna ze záložek totiž přečte soubor ve chvíli, kdy jej druhá ještě nestihla celý zapsat, takže obsah nebude kompletní.
Uvedený kód tedy není bezpečný, pokud se v jednu chvíli provádí vícekrát (tedy ve více vláknech). Což na internetu není nic neobvyklého, často v jednu chvíli odpovídá server velkému počtu uživatelů. Takže zajistit, aby vaše aplikace fungovala spolehlivě i při provádění ve více vláknech (thread-safe), je velmi důležité. Jinak dojde ke ztrátě dat a vzniku těžko odhalitelných chyb.
Jak ale vidíte, nativní PHP funkce pro čtení a zápis souborů izolované a atomické nejsou.
Jak používat SafeStream?
SafeStream vytváří bezpečný protokol, pomocí něhož lze izolovaně číst a zapisovat soubory prostřednictvím
standardních PHP funkcí. Stačí jen uvést nette.safe:// před jménem souboru:
file_put_contents('nette.safe://soubor', $s);
$s = file_get_contents('nette.safe://soubor');
SafeStream zajišťuje, že v jednu chvíli může do souboru zapisovat maximálně jedno vlákno. Ostatní vlákna čekají ve frontě. Pokud žádné vlákno nezapisuje, může soubor číst paralelně libovolný počet vláken.
S protokolem lze používat všechny běžné PHP funkce, například:
// 'r' znamená otevřít pouze pro čtení
$handle = fopen('nette.safe://file.txt', 'r');
$ini = parse_ini_file('nette.safe://config.ini');
Omezení
SafeStream izoluje čtení a zápis obsahu souborů, ale nedokáže udělat úplně každou operaci atomickou. Mějte na paměti tyto hranice:
- Informace o souboru nejsou izolované. Funkce, které jen zjišťují metadata, jako
file_exists(),filesize()nebois_file(), se zamykání neúčastní. Mohou vrátit informace o souboru, který právě jiné vlákno zapisuje. - Mazání otevřeného souboru ve Windows. Na rozdíl od Unixu Windows nedovolí smazat soubor, který má právě jiné
vlákno otevřený, takže
unlink('nette.safe://soubor')může selhat. - Automatický rollback nedokončeného zápisu. Pokud zápis selže v půli (například se zaplní disk), SafeStream při zavření souboru zkrátí soubor zpět na velikost, kterou měl před začátkem zápisu, takže po sobě nikdy nenechá částečně zapsaná data.