Bug v Nette cache – FileStorage.php
- Honza Kuchař
- Člen | 1662
Ahoj, dneska jsem si hrál s keší a narazil jsem na takový problémek. Následující k´d mi nechal v tempech prázdný soubor (0bytů):
$cache = Environment::getCache("Test");
$cache["test"] ="test";
unset($cache["test"]);
Po odstranění zavináče v FileStorage.php:255 jsem se dozvěděl „Permision deined“. Ale já jsem pod Win a Apache běží pod uživatelem system. Takže může všude. Hmmm. Ve funkci readMeta() se soubor uzamkne přečte vrátí. Jenomže není uzavřený a uzamčení pořád trvá. Odemčít se měl ten soubor až v remove() na řádku 256. Jenomže o řádek před tím se zamčený soubor pokouším smazat.
Tzn. upravená a funkční verze remove() je pouze s přehozenými řádky:
/**
* Removes item from the cache.
* @param string key
* @return bool TRUE if no problem
*/
public function remove($key)
{
$cacheFile = $this->getCacheFile($key);
$meta = $this->readMeta($cacheFile, LOCK_EX);
if (!$meta) return TRUE;
ftruncate($meta[self::HANDLE], 0);
fclose($meta[self::HANDLE]);
@unlink($cacheFile); // intentionally @
return TRUE;
}
- David Grudl
- Nette Core | 8228
Ano, FileStorage pod Windows má problém kvůli nemožnosti mazat uzamčený soubor. Pokud jej odemkneš a pak teprve smažeš, není to atomické, tj. je tu šance, že během té doby došlo ke změně obsahu souboru a maže se něco novějšího. Z toho důvodu je tam to truncate(), aby bylo možné soubor se zámkem pod Windows vůbec zlikvidovat.
Samozřejmě je otázka, jestli zmíněná neatomičnost může mít nějaký negativní vedlejší efekt. Kromě toho, že se smaže něco jiného.
- Honza Kuchař
- Člen | 1662
A co tam dát podmínku že pokud velikost souboru == 0?
No vlastně stále je to neatomické. :/ Tak co udělat kompromis: tuto malinkatou neatomičnost vyvolat pouze pokud je skript spuštěn pod Windows. Já mám server na Windows a dohromady s tím MiltipleFileUploaderem by to vytvářelo opravdu hodně souborů.
Nebo by šly soubory uzamykat pomocí druhého souboru .lock
.
Ale to už je dost práce na víc.
Ještě dotaz, když mám atomickou cache: 2 procesy současně začnou zapisovat, tak jeden musí počkat, nebo se jeden prostě nezapíše? Nebo se vyhodí chyba?
- Honza Kuchař
- Člen | 1662
Asi to udělám jednoduše: v použití MultipleFileUploaderu je tak asi 1/10000000000, že vůbec trefí 2× po sobě stejný název položky v cache. Takže tam napíšu upravený FileStorage, na který se cache přepne, pokud jede pod Windows.
- Honza Kuchař
- Člen | 1662
Jinak zdá se, že už tento nápad někdo měl: http://cz2.php.net/…on.flock.php#…
Zatím budu používat klasické filestorage. Až bude troška času, zkusím to přepsat tímto stylem. CO vy na to?
A v tomto stylu zapisování a čtení cache ( http://cz2.php.net/…on.flock.php#… ):
<?php
$fp = fopen($logFileName, 'a');
$canWrite = false;
//Waiting until file will be locked for writing
while (!$canWrite) {
$canWrite = flock($fp, LOCK_EX);
//Sleep for 0 - 2000 miliseconds, to avoid colision
$miliSeconds = rand(0, 20); //1 u = 100 miliseconds
usleep(round($miliSeconds*100000));
}
//file was locked so now we can store information
fwrite($fp, $toSave);
fclose($fp);
?>
Jednoduché a účinné.
Editoval honzakuchar (20. 8. 2009 8:34)
- David Grudl
- Nette Core | 8228
Celý ten cyklus se dá napsat jednodušeji:
flock($fp, LOCK_EX);
:-) (ano, flock čeká automaticky). Ale
hlavně to nesouvisí s titulním problémem.
Tak co udělat kompromis: tuto malinkatou neatomičnost vyvolat pouze pokud je skript spuštěn pod Windows.
No, tohle není úplně 100%, protože problém se týká NTFS (a asi i FAT), nikoliv přímo Windows. Sice v drtivé většině případů platí Windows = NTFS, Linux = ext3, ale je to hra s pravděpodobností.
- David Grudl
- Nette Core | 8228
Zkusil jsem updatnout FileStorage. Můžeš to prosím důkladně otestovat? Případně i na Linuxu, jestli máš možnost.
- Honza Kuchař
- Člen | 1662
Na Linuxu možnost nemám. Na php.net jsem se dočetl, že FAT vůbec nepodporuje uzamykání souborů. (vždy vrátí false)
Testovat jdu hnedka ;)
- Honza Kuchař
- Člen | 1662
Cache se zdá být v pořádku, ale já mám v MultipleFileUploaderu totální neatomičnost. :( Ale nevím jak to vyřešit.
Soubory jako takové se na server (zdá se dostanou v pořádku). Ale musím
od nich ještě chvíli skladovat meta informace – serializovaný
HttpUploadedFile
.
Dělám to následujícím způsobem (upozorňuji, že následující řešení je úplně špatně a proto ho prosím nikdo nepoužívejte!)
$store = self::getData($token); // vrátí pole vytažené z cache tzn. volá něco jako return $cache[$token]
$store[] = new HttpUploadedFile(xxx); // Přidám tam informace
self::saveData($token); // zase zapíše do cache.
Jenomže pokud je mi známo, tak se soubor v cache odemkne hned po tom co si ty data vytáhnu. Ale já je pořebuji mít uzamčené po celou dobu až dokud neskoční to načtení – přidání – uložení. Tzn. jak v cache atomicky upravovat data? Jak se tak dívám ono by klidně stačilo kdybych byl schopný získat včechny cachované položky z jednoho namespace.
- h4kuna
- Backer | 740
<?php
$cache = Environment::getCache("Test");
$cache["test"] ="test";
unset($cache["test"]);
?>
David Grudl napsal(a):
No, tohle není úplně 100%, protože problém se týká NTFS (a asi i FAT), nikoliv přímo Windows. Sice v drtivé většině případů platí Windows = NTFS, Linux = ext3, ale je to hra s pravděpodobností.
Ja osobne jedu na ReiserFS a vsak to se chova nejspise stejne jako ext3.
PHP 5.2.10–2
Nette 5ecb8df
Otestoval jsem to v temp/c-Test mi nic nezustalo pro ReiserFS TRUE
honzakuchar napsal(a):
Na Linuxu možnost nemám. Na php.net jsem se dočetl, že FAT vůbec nepodporuje uzamykání souborů. (vždy vrátí false)
ani FAT pod linuxem nefunguje v tempu zustal soubor „_test“ (flashdisk) FALSE
NTFS pod linuxem v pohode, asi to ale nebude smerodatny pac mi to fachalo i u predchozi revize 8f16ab2 TRUE
Doufam ze to pomohlo
Editoval matata (20. 8. 2009 14:37)
- David Grudl
- Nette Core | 8228
Podle mých stress testů to na Win/NTFS a Linux/ext3 taky valí dobře.
honzakuchar napsal(a):
Cache se zdá být v pořádku, ale já mám v MultipleFileUploaderu totální neatomičnost. :( Ale nevím jak to vyřešit.
Používáš cache na něco, na co se nehodí. U cache totiž platí jedno pravidlo – všechno, co tam uložíš, může být v následující milisekundě klidně smazáno. Proto existuje i úložiště DummyStorage – správně napsaná aplikace s ním musí fungovat.
To, co hledáš, je session.
- Honza Kuchař
- Člen | 1662
Jenomže session s Flashem použít nemůžu. Kdyby to šlo, tak bych to už dávno udělal. Viz http://bugs.adobe.com/…browse/FP-78 . Ale tohle není až zase takový problém, to bych obešel, že bych si ten identifikátor předal někde v datech, ale potom je jako výchozí v Nette Session zapnuto, že se ověřuje prohlížeč jazyk a ještě cosi. Tzn. odhlásí mě to.
Existuje v Nette něco jako Storage dat, které je přístupné od všech klientů naráz? (tedy jakoby globální – to co teď nahrazuji cachí?) Nebo by bylo něco takového potřeba napsat?
Editoval honzakuchar (20. 8. 2009 18:38)
- David Grudl
- Nette Core | 8228
To ověřování v session lze vypnout, pokud je problém jen v tomto.
Mechanismus pro ukládání cache by v podstatě šlo použít pro
ukládání trvalejších dat. Ale určitě bych to šoupl do extra adresáře,
řekněme třeba uploadData
. Aby zámek byl trvalý, šlo by
použít TemplateCacheStorage:
$cache = new Cache(new TemplateCacheStorage(__DIR__ . '/uploadData/'));
$cache[$key] = $data;
...
$dataAccess = $cache[$key]; // POZOR! Zde je trik - $dataAccess je pole s prvkem `handle`, tedy handle souboru se zámkem
$cache[$key] = $newData; // zapíšu nová data
fclose($dataAccess['handle']);
píšu to z hlavy, ale mohlo by to tak nějak fungovat.
- Honza Kuchař
- Člen | 1662
To ověřování v session lze vypnout, pokud je problém jen v tomto.
Vím, ale připadí mi nevhodné kvůli odesílacímu políčku snižovat bezpečnost celé aplikace.
Mechanismus pro ukládání cache by v podstatě šlo použít pro ukládání trvalejších dat.
Přesně to jsem myslel. Nepsat to znova jen upravit.
šlo by použít TemplateCacheStorage
Děkuji za tip. Zítra vyzkouším. Popřípadě se zkusím tuto
funkcionalitu nějak hezky zaobalit. Protože jak tak vidím, tak cache
používám úplně špatně. Zatím jsem vždy cache používal jako
session společnou pro všechny uživatele. Tzn. pokud bych použil
DummyStorage
rozhodně by to nefungovalo. Davide moc děkuji za ten
příklad s tím DummyStorage už je mi přesně jasné, kdy a jak cache
využít. ;) Možná tu větu přidat do dokumentace? :)
- David Grudl
- Nette Core | 8228
honzakuchar napsal(a):
Vím, ale připadí mi nevhodné kvůli odesílacímu políčku snižovat bezpečnost celé aplikace.
Přemýšlím, jestli by to nešlo vypnout jen pro ty Flash requesty…
- Honza Kuchař
- Člen | 1662
Nevím, nezkoušel jsem. Někde tady na fóru jsem se dočetl, že se to musí vypnout pro celou app. (ale nevím jestli je to ještě pořád pravda…) Ale i tak, je session thread-safe? Myslím, že ne. Tzn. když budu mí od jednoho uživatele 3 fronty, každou na 2 vláknech, tak to dopadne špatně. Protože teď má každá fronta alespoň vlastní soubor, takže pokud jede na jednom vlákně, tak to jede perfektně…
- David Grudl
- Nette Core | 8228
Session se na začátku požadavku zamkne a je dostupná jen jednomu vláknu, takže funguje přesně jak potřebuješ.
- Honza Kuchař
- Člen | 1662
Mno tak to by stálo za zvážení… Ale chtělo by to nějak vyřešit to vypínání toho ověřování prohlížeče a spol. jenom na tom jednom requestu. (nezkoušel jsem to) Nebo už to tak funguje?