GB: Dateizugriff sicher blocken
Felix Riesterer
- php
Liebe Selfer,
ich erstelle gerade ein Gästebuch. Es klappt alles ganz gut... solange nicht zwei Besucher "zufällig" zur selben Zeit ihre Postings abschicken (ist zwar noch nicht vorgekommen, aber wer weiß?).
Die Dokumentation zu PHP als auch das Forum hier haben mir nicht zufriedenstellend helfen können. flock() scheint problematisch zu sein (weil nicht auf allen OS identisch im Verhalten)...
Wenn mein Script aufgerufen wird, dann prüft es, ob eine Lock-Datei vorhanden ist. Theoretisch soll es dann so lange warten, bis diese Datei nicht mehr existiert, oder eben fünf Sekunden vorbei sind, um nicht in einen Timeout zu geraten.
Leider bleibt es beim Auffinden der Lock-Datei der Meinung, dass die Lock-Datei existiere, selbst wenn sie längst gelöscht wurde! Kennt PHP soetwas wie einen Cache? Hier mein Code:
$start = time();
$jetzt = time();
while($jetzt-$start < 5)
{
if(!file_exists($lockdatei)) // diese Bedingung ändert sich nie... :-(
{
echo "<!-- nicht gefunden: Lockdatei! //-->\n";
break;
}
$jetzt = time();
}
Wie erreiche ich, dass die Bedingung in der if()-Abfrage auf den tatsächlichen Stand aktualisiert wird? Ich kann innerhalb von 5 Sekunden die Datei per Hand löschen, das Script erkennt das aber nicht. file_exists() "merkt" sich wohl sein Ergebnis für den Rest der Laufzeit...
Bitte Hilfe!
Liebe Grüße aus Ellwangen,
Felix Riesterer.
你好 Felix,
sorry, wenn ich das jetzt so sage, aber es muss sein: bitte lass das. Dein
Provider wird es dir danken. Du hast zwei wirklich boese Sachen gebaut:
einen Busy-Wait und eine Race-Condition.
Zu 1) Du hast das ganze in eine while()-Schleife verpackt, in der du
jedesmal ein file_exists() machst. Das file_exists() wird uebrigens nicht
gecached, aber egal; was ich sagen wollte: du belegst damit zu 100% die
CPU -- fuer uU 5 ganze Sekunden!
Zu 2) Der stat()-Call und das anlegen der Lockdatei sind nicht atomar, es
kann also sein, dass zwei Scripte den gleichen Lock belegen und denken, sie
haetten exklusiven Zugriff. Eine klassische race condition.
再见,
CK
你好 Felix,
jetzt hab ich die Haelfte vergessen...
http://php.net/clearstatcache -- da steht genau beschrieben,
_was_ gecached wird. file_exists() gehoert nicht dazu.
Mach es lieber richtig, mit flock(). Es ist doch muessig auf veraltete
Systeme Ruecksicht zu nehmen (Win9x, Fat32) die eh nie im Leben als
Server-Betriebssystem in Frage kommen.
再见,
CK
Hi Christian,
gut, dass Du das schreibst! Ich habe da anscheinend wirklich etwas nicht verstanden. Was empfiehlst Du mir? Muss ich es letzten Endes per flock() machen, oder weißt Du noch eine Alternative?
Auf php.net stand in einem User-Kommentar, dass das Anlegen eines Verzeichnisses mittels mkdir() besser wäre, als mit Dateien herumzupfuschen. Aber da steig ich dann aus.
Danke für Deine schnelle Antwort. Bin begierig meinen Mist zu verbessern!
Liebe Grüße aus Ellwangen,
Felix Riesterer.
你好 Felix,
gut, dass Du das schreibst! Ich habe da anscheinend wirklich etwas nicht
verstanden. Was empfiehlst Du mir? Muss ich es letzten Endes per flock()
machen, oder weißt Du noch eine Alternative?
Ja, es waere wirklich sinnvoll, das mit flock() zu machen, das schrub ich
ja.
Auf php.net stand in einem User-Kommentar, dass das Anlegen eines
Verzeichnisses mittels mkdir() besser wäre, als mit Dateien
herumzupfuschen. Aber da steig ich dann aus.
Da ist das gleiche Problem mit der race condition. Ich versuchs nochmal zu
erklaeren. Prozess A guckt nach, ob eine Lock-Datei existiert. Sie
existiert nicht, daher geht er in den “kritischen Bereich”. In dem
Augenblick entscheidet der Scheduler, dass er Prozess A schlafen legen
will und Prozess B bearbeitet. Prozess B guckt nach, ob die Lock-Datei
existiert -- nein, sie existiert nicht. Damit ist auch Prozess B im
kritischen Bereich. Und schon hast du ein Problem.
再见,
CK
Hallo Felix,
ich erstelle gerade ein Gästebuch. Es klappt alles ganz gut... solange nicht zwei Besucher "zufällig" zur selben Zeit ihre Postings abschicken (ist zwar noch nicht vorgekommen, aber wer weiß?).
Die Dokumentation zu PHP als auch das Forum hier haben mir nicht zufriedenstellend helfen können. flock() scheint problematisch zu sein (weil nicht auf allen OS identisch im Verhalten)...
Wenn mein Script aufgerufen wird, dann prüft es, ob eine Lock-Datei vorhanden ist. Theoretisch soll es dann so lange warten, bis diese Datei nicht mehr existiert, oder eben fünf Sekunden vorbei sind, um nicht in einen Timeout zu geraten.
Vergiss es. Das ist nicht zuverlässig, da in dem Moment, bei dem das Gästebuch denkt, die Lock-Datei sei gelöscht worden, die Lock-Datei von einem anderen Prozess wieder angelegt sein könnte.
Zuverlässig ist nur das Löschen einer Datei oder dio_open() in Verbindung mit O_EXCL, wobei das nicht unter Windows funktioniert. Symbolische Links wären auch möglich.
Viele Grüße
Patrick Canterino
你好 Patrick,
Zuverlässig ist nur das Löschen einer Datei oder
dio_open() in Verbindung mit
O_EXCL, wobei das nicht unter Windows funktioniert. Symbolische Links
wären auch möglich.
Nein, abhaengig vom Dateisystem ist das anlegen der Datei auch _nicht_
atomar.
再见,
CK
Liebe Selfer,
also dann wohl doch flock()?
Und wie mache ich das richtig? Im Archiv stand da sehr verworrenes Zeug. Und auf PHP.net sind Beispiele, in denen steht, dass sie nicht funktionieren...
Liebe Grüße aus Ellwangen,
Felix Riesterer.
Liebe Selfer,
auf http://de.php.net/manual/de/function.flock.php habe ich gelesen, dass auch flock() mit race conditions probleme hat (Comment von pentek_imre at mailbox dot hu). Beschrieben wird eine Situation von file(lck) mit anschließendem flock(lck). Die Aufrufe in der Warteschlange haben eine alte Kopie der Datei lck und werden beim Speichern die neuesten Eingaben darin überschreiben.
Was mach ich jetzt?
Liebe Grüße aus Ellwangen,
Felix Riesterer.
Hello,
auf http://de.php.net/manual/de/function.flock.php habe ich gelesen, dass auch flock() mit race conditions probleme hat (Comment von pentek_imre at mailbox dot hu).
Das Problem von pentek_imre ist, dass er keine atomare Prozessmenge aufbaut. Die Einzelprozesse sind weiter teilbar, bzw. arbeiten mit unterschiedlichen Handles mit Zeitlücke.
Der betroffene Dateibereich darf erst dann eingestellt werden (fseek()) wenn das Lock sicher greift, und nicht umgekehrt. Es gibt bei mndatory Locking, so wie es in C und PHP ungestzt wird, bei direkter Verwendung des zuschützenden Files leider keine Übernahmemöglichkeit des Handles von einem Prozess auf den nächsten.
Harzliche Grüße aus http://www.annerschbarrich.de
Tom
Liebe Selfer,
habe mir was überlegt und möchte eure Meinung wissen. Mein Vorschlag zu obigem Problem:
Man flock()ed nicht die eigentlich zu bearbeitende Datei, sondern legt eine Dummy-Datei auf dem Server ab, die dort auch verbleibt. Wenn diese Datei geflock()ed ist, dann muss das Script warten, bis sie wieder freigegeben wird. Das soll Lesezugriffe à la $f=file() vermeiden, da dadurch in einer race condition möglicherweise Daten verloren gehen könnten. Meine Dummy-Datei heißt "post.lock".
$lockdatei = "post.lock";
$datendatei = "daten.dat";
flock($lockdatei, LOCK_EX);
$gaestebuch = file($datendatei);
// $gaestebuch wird modifiziert
$schreibdatei = fopen($datendatei, "wb");
foreach($gaestebuch as $schreibwert) fputs($schreibdatei, trim($schreibwert)."\r\n");
fclose($datei);
fclose($lock);
Was meint ihr? Habe ich eine mögliche race condition damit etwas entschärft?
Liebe Grüße aus Ellwangen,
Felix Riesterer.
你好 Felix,
Man flock()ed nicht die eigentlich zu bearbeitende Datei, sondern legt
eine Dummy-Datei auf dem Server ab, die dort auch verbleibt. Wenn
diese Datei geflock()ed ist, dann muss das Script warten, bis sie
wieder freigegeben wird.
Ja, so kannst du das machen.
$lockdatei = "post.lock";
$datendatei = "daten.dat";
flock($lockdatei, LOCK_EX);
Das flock() musst du auf einen File descriptor anwenden, also so:
$lockdatei = "post.lock";
$lockh = fopen($locdatei,"a+") or die("error in open()!");
flock($lockh,LOCK_EX) or die("Error in locking!");
Vergiss vor allem die Fehlerbehandlung nicht. Was nuetzt dir das File
locking, wenn du aufgrund fehlender Fehlerbehandlung trotzdem in den
kritischen Bereich laeufst?
再见,
CK
Hello,
Ja, so kannst du das machen.
$lockdatei = "post.lock";
$datendatei = "daten.dat";
flock($lockdatei, LOCK_EX);Das flock() musst du auf einen File descriptor anwenden, also so:
$lockdatei = "post.lock";
$lockh = fopen($locdatei,"a+") or die("error in open()!");flock($lockh,LOCK_EX) or die("Error in locking!");
Bei PHP wirst Du den 'or'-Zweig nicht mehr zu sehen bekommen.
flock() liefert in der Default-Einstellung erst false, wenn das Script-Timeour erreicht ist.
Um Deiner Anweisung Sinn einzuhauchen muss man
flock($lockh,LOCK_EX+Lock_NB) or die("Error in locking!");
schreiben.
Dann wird genau ein Lockzyklus versucht. 'Zyklus' bedeutet, dass das OS entscheidet, wieviele Locks auf der untersten Schicht wirklich versucht werden. Standard wären 5 einzeln atomare Lockversuche. Die haben zwischen sich dann kleine zeitliche Lücken, damit andere Prozesse ggf. dazwischenschlüpfen können.
Das ist aber ggf. bei unterschiedlichen OS auch unterschiedlich geregelt. Ich kann da bisher nur mit hoher Sicherheit für DOS-Derivate und Novell sprechen. Versuche taugen hier i.d.R. wenig. Da muss man schon in den Assembler-Code einsteigen.
@CK:
Wir hatten das Thema aber schon einmal aufgegriffen und ich bat um Zurückstellung. Ich habe es noch nicht vergessen. Leider habe aber bisher weder Zeit für die angemessenen Dokus noch Geld für die notwendige Literatur gehabt. Wenn die hier nur herumliegt und schimmelt, bringt das auch nichts.
Harzliche Grüße aus http://www.annerschbarrich.de
Tom
Hello,
flock($lockh,LOCK_EX+Lock_NB) or die("Error in locking!");
Für die 'Genau-Abschreiber' und Pedanten:
flock($lockh, LOCK_EX + LOCK_NB) or die("Error in locking!");
War natürlich so falsch. Die Konstante LOCK_NB muss auch in Versalien geschrieben werden.
Harzliche Grüße aus http://www.annerschbarrich.de
Tom
你好 Tom,
Bei PHP wirst Du den 'or'-Zweig nicht mehr zu sehen bekommen.
flock() liefert in der Default-Einstellung erst false, wenn das
Script-Timeour erreicht ist.
Es gibt so viele Gruende, warum ein flock() fehlschlagen kann, das kannst du
gar nicht ueberblicken.
再见,
CK
Hello CK,
Bei PHP wirst Du den 'or'-Zweig nicht mehr zu sehen bekommen.
flock() liefert in der Default-Einstellung erst false, wenn das
Script-Timeour erreicht ist.Es gibt so viele Gruende, warum ein flock() fehlschlagen kann, das kannst du
gar nicht ueberblicken.
Welche könnten das denn mit der Einstellung BLOCKING (ist Default) sein?
Da fiele mir eigentlich nur ein ungültiges Handle ein.
Aber wenn es als gültiges Handle besorgt worden ist, und man es dann für flock() verwendet, wird es benutzt, auch wenn es die Datei inzwischen nicht mehr gibt. Ob das nun so intelligent ist, weiß ich nicht.
So ist aber mWn das Verhalten von PHP.
Was mich nur sehr ärgert, ist die schlechte Harmonisierung von flock() und den dio-Funktionen. Wenn eine Datei mandatory locked ist, und man versucht dann ein flock(LOCK_NB), dann hängt das flock trotzdem bis zum Timeout oder ggf. bis zur Freigabe durch die dio-Funktion. Das sollte von den PHPlern korrigiert werden und nicht als "Feature" abgetan werden!
Harzliche Grüße aus http://www.annerschbarrich.de
Tom
Hello,
ich erstelle gerade ein Gästebuch. Es klappt alles ganz gut... solange nicht zwei Besucher "zufällig" zur selben Zeit ihre Postings abschicken (ist zwar noch nicht vorgekommen, aber wer weiß?).
Die Dokumentation zu PHP als auch das Forum hier haben mir nicht zufriedenstellend helfen können. flock() scheint problematisch zu sein (weil nicht auf allen OS identisch im Verhalten)...
Man sollte schon wissen, auf welchem Betriebssystem sein eigenes Script läuft.
flock() funktioniert sowohl bei Linux/Unix als auch bei Windows seit längerer Zeit schon zufriedenstellend. Voraussetzung ist aber, dass alle Prozesse sich daran halten und Du auch weißt, wie man es einsetzt.
Siehe hierzu auch http://selfhtml.bitworks.de --> Adressverwaltung
Dort ist eine universelle Lockingfunktion eingebaut.
Irgendwann wird sicher auch mal mein Artikel zum Thema fertig werden. Kann sich nur noch bis nächstes Jahr hinziehen ;-)
Harzliche Grüße aus http://www.annerschbarrich.de
Tom
Liebe Selfer Christian Kruse & Tom & Patrik Canterino,
eure Erläuterungen und Verbesserungen haben mir sehr weitergeholfen. Jetzt verstehe ich die Problematik mit flock() wesentlich besser und kann mein Script entsprechend sicherer machen.
Danke!
Liebe Grüße aus Ellwangen,
Felix Riesterer.