Felix Riesterer: GB: Dateizugriff sicher blocken

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.

  1. 你好 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

    --
    Wenn auf Erden alle das Schoene als schoen erkennen, so ist dadurch schon das Haessliche bestimmt.
    http://wwwtech.de/
    1. 你好 Felix,

      jetzt hab ich die Haelfte vergessen...

      1. http://php.net/clearstatcache -- da steht genau beschrieben,
        _was_ gecached wird. file_exists() gehoert nicht dazu.

      2. 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

      --
      To define recursion, we must first define recursion.
      http://wwwtech.de/
    2. 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.

      1. 你好 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

        --
        Es ist uns nicht möglich, in einem Bereich unseres Lebens richtig zu verhalten, wenn wir in allen anderen falsch handeln. Das Leben ist ein unteilbares Ganzes.
        http://wwwtech.de/
  2. 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

    --
    "Wien bleibt Wien - und das ist wohl das schlimmste, was man über diese Stadt sagen kann." (Alfred Polgar)
    1. 你好 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

      --
      Auf der ganzen Welt gibt es nichts Weicheres und Schwaecheres als Wasser. Doch in der Art, wie es dem Harten zusetzt, kommt nichts ihm gleich.
      http://wwwtech.de/
  3. 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.

  4. 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.

    1. 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

      --
      Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
      Nur selber lernen macht schlau
  5. 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.

    1. 你好 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

      --
      Wenn du gehst, gehe. Wenn du sitzt, sitze. Und vor allem: schwanke nicht!
      http://wwwtech.de/
      1. 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

        --
        Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
        Nur selber lernen macht schlau
        1. 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

          --
          Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
          Nur selber lernen macht schlau
        2. 你好 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

          --
          Microsoft: Where do you want to go today?
          Linux: Where do you want to go tomorrow?
          FreeBSD: Are you guys coming, or what?
          http://wwwtech.de/
          1. 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

            --
            Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
            Nur selber lernen macht schlau
  6. 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

    --
    Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
    Nur selber lernen macht schlau
  7. 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.