Co se vlastně myslí pod atomickými operacemi nebo rozumí pod pojmem „thread-safe“? Začněme jednoduchým příkladem:

$original = str_repeat('LaTrine', 10000);
$counter = 1000;

while ($counter--) {
	// write
	file_put_contents('soubor', $original);

	// read
	$content = file_get_contents('soubor');

	// compare
	if ($original !== $content)
		die('ERROR');
}

Dokola zapisujeme a následně čteme stále tentýž řetězec. Může se zdát, že volání die('ERROR') nemůže nikdy nastat. Opak je pravdou. Schválně si zkuste tento skript spustit ve dvou oknech zároveň. Error se dostaví prakticky okamžitě.

Proč tomu tak je, nedávno vysvětloval Jakub Vrána. Uvedený kód není bezpečný (safe), pokud se v jednu chvíli provádí vícekrát (tedy ve více vláknech = threads). Což na internetu není nic neobvyklého, často se v tentýž okamžik pokusí více lidí připojit k jednomu webu. Takže psaní thread-safe aplikací je velmi důležité.

Je třeba zajistit, aby se funkce file_get_contents & spol. vykonávaly atomicky. Pro Nette jsem napsal třídu, obsahující atomické alternativy těchto funkcí. Do stavu ERROR se s nimi nikdy nedostanete. Uvedený příklad stačí upravit takto:

while ($counter--) {
	// write
	NSafeFile::write('soubor', $original);

	// read
	$content = NSafeFile::read('soubor');

	// compare
	if ($original !== $content)
		die('ERROR');
}

Musím říci, že najít ten správný postup byl docela oříšek. Své by o tom mohl vykládat třeba Johno, který SafeCache předělával snad pětkrát, přesto poslední verze má stále chyby. …a nebo taky já, který těch předělávek má na krku ještě víc.

Metoda flock()

Funkce flock, která je k řešení těchto situací určená. Potíž je v tom, že má tolik háčků, až je skoro nepoužitelná. Zamknout není možné soubor před jeho otevřením či vytvořením, stejně tak smazat jej lze až po uzavření (platí pro platformu Windows). A tím pádem veškerá atomicita jde do háje.

Metoda rename()

Existuje další způsob, spočívající v přejmenovávání dočasně vytvořeného souboru – zmiňuje jej Jakub Vrána. Zdál se mi ideální, je však nepoužitelný, pokud chceme přepisovat obsah souboru – pak nelze zajistit atomicitu.

Metoda lock-files

Dále je tu princip založený na vytváření lock-files. Tedy dočasných souborů nulové délky či adresářů, jejichž přítomnost značí přítomnost zámku. Tato metoda je v podstatě nejjednodušší. Zvyšuje však počet souborových operací a celý proces se tak značně zpomalí. Dále je potřeba vyřešit problém s (ne)odstraněním lock-files, například po pádu Apache apod.

Řešení NSafeFile

Nakonec se mi (snad) podařilo vytvořit novou a spolehlivou verzi kombinující první dva postupy. Vyhnul jsem se lock-files, výsledek je tedy znatelně rychlejší. Popis všech „triků“ najdete ve zdrojovém kódu.

Aktualizace: NSafeFile už je historií, stáhněte si nástupce Nette\IO\SafeStream.