mod: Datei organisisert sperren

Hallo,

es geht konkret um die in ein ErrorHandling-Objekt implemetierte Errorlog-Datei für schwerwiegende Fehler gehen. Es geht um die Problematik, wenn 2 oder mehrere Script gleichzeitig auf error.log zu greifen. Die Scripte sollen nacheinander ihre Fehlermedlung ins die error.log schreiben nicht gleichzeitig, dazu habe ich flock() verwendet:

$this->logPath=' ... ';
$file = fopen( $this->logPath, 'c' ) or die('Unable to open errorlog file');
$success = flock( $file, LOCK_EX );

while( !$success ) {
    $success = flock( $file, LOCK_EX );
}

fwrite( $file, $this->logMessage ) or die('Unable to write in errorlog file');
flock( $file, LOCK_UN );
fclose( $file );

Die while()-Schleife könnte in eine Endlosschleife enden - kann man das verhintern und wie ? - vielleicht mit einer maximalen Anzahl von Aufrufen der flock()-Funktion ?

Gruß mod.

  1. Tach!

    $success = flock( $file, LOCK_EX );
    while( !$success ) {

    Lies dir bitte die Beschreibung der Funktion durch, besonders den Teil zu blocking/non-blocking. Und der Dateisperren-Artikel kann auch nicht schaden.

    dedlfix.

  2. Hello,

    es geht konkret um die in ein ErrorHandling-Objekt implemetierte Errorlog-Datei für schwerwiegende Fehler gehen. Es geht um die Problematik, wenn 2 oder mehrere Script gleichzeitig auf error.log zu greifen. Die Scripte sollen nacheinander ihre Fehlermedlung ins die error.log schreiben nicht gleichzeitig, dazu habe ich flock() verwendet:

    Das ist für Error-Logs eigentlich überskaliert.
    Die üblichen Betriebssysteme halten immer noch den Filetyp "Textdatei" vor. Das ist eine rein serielle Datei, die man dann im Filemode Append ('a') oder Append/Read ('a+'), zur Sicherheit für die Binärfestigkeit auch ('ab') oder ('ab+') öffnen sollte, bzw. sogar eigentlich nur kann.

    Hier kümmert sich das Filesystem automatisch um das Positionieren des Dateizeigers _ans__ENDE_ der Datei und das Serialisieren der Zugriffe unterschiedlicher Prozesse.

    [PHP hatte hier zeitweise einen Fehler und man konnte den Dateizeiger auch zum Schreiben trotzdem vor dem Ende positionieren. Der ist aber mWn inzwischen behoben. Steht der Dateizeiger nicht am Ende der Datei, wenn der Prozess an der Reihe ist, wird er automatisch dorthin verschoben.)

    Du musst Dir daher um das Locking derartiger Dateien keine Gedanken machen.

    Anders ist es, wenn Du ein blockorientierten Zugriff wünscht, also auch wahlfrei positionieren können willt zum Schreiben (und zum Lesen, aber das ist für die vorhandenen Daten unschädlich, wenn auch nicht unherheblich).

    Die 'a'-Modi haben zudem noch den Vorteil, dass die Datei auch angelegt wird, wenn sie noch nicht vorhanden war. Man muss also nicht erst prüfen, ob die Datei existiert, bevor man sie öffnet - was ja aus dem Gesichtspunkt von TOCTTOU auch nicht in zwei Schritten geschehen darf!

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Danke für eure Hilfe!

      Ich habe mal 2 Scripte mit Zugriff auf die Datei gleichzeitig laufen lassen.
      Es ist schon besser flock() zu verwenden, wenn im Log mehrere Fehlermeldungen verschiedener Prozesse gespeichert werden sollen, also sortiert:

      $this->getLogPath();
      $file = fopen( $this->logPath, 'ab+' );
      flock( $file, LOCK_EX );
      fwrite( $file, $this->logMessage );
      flock( $file, LOCK_UN );
      fclose( $file );

      Etwas Anderes - welche Daten zur Fehleranalyse wären ebenfalls noch gut geeignet in der Log-File mit abgespeichert zu werden?

      1. Hello,

        Ich habe mal 2 Scripte mit Zugriff auf die Datei gleichzeitig laufen lassen.
        Es ist schon besser flock() zu verwenden, wenn im Log mehrere Fehlermeldungen verschiedener Prozesse gespeichert werden sollen, also sortiert:

        Wie sollen die denn sortiert werden? Es wird beim Filemode 'ab+' immer ein Schreibauftrag nach dem nächsten ausgeführt. Die werden erst beendet, bevor der nächste an die Reihe kommt. Man darf den Schreibauftrag selber dafür selbstverständlich nicht als Schleife ausführen, sondern muss den gesamten Stream in eins in Auftrag geben! Bei mehreren "Zeilen" also erst alle zusammenbauen zu einem Stream und dann erst in Auftrag geben.

        $this->getLogPath();
        $file = fopen( $this->logPath, 'ab+' );
        flock( $file, LOCK_EX );
        fwrite( $file, $this->logMessage );

        flock( $file, LOCK_UN );

        Das LOCK_UN  _musst_  Du dir schenken. Durch fclose() wird das File auch entsperrt, und zwar in einem abgeschlossenen Auftrag (atomarisiert).

        fclose( $file );

        Sonst müsstest Du der Ordnung halber vor dem Entsperren noch ein Flush() auslösen, damit ggf. benutzte Buffer vor dem Entsperren noch in Richtung Datei geleert werden.

        Das übernimmt fclose() aber schon und es besteht bei ausschließlicher Benutztung von fclose() nicht die Gefahr, dass man dann doch in der zeitlichen Abfolge der unterschiedlichen Teilprozesse etwas durcheinander bringt.

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Das LOCK_UN  _musst_  Du dir schenken. Durch fclose() wird das File auch entsperrt, und zwar in einem abgeschlossenen Auftrag (atomarisiert).

          siehe Changelog: http://de.php.net/manual/de/function.flock.php
          seit php 5.3.2 muss wieder manuell entsperrt vor fclose()

          fclose( $file );

          Sonst müsstest Du der Ordnung halber vor dem Entsperren noch ein Flush() auslösen, damit ggf. benutzte Buffer vor dem Entsperren noch in Richtung Datei geleert werden.

          gut, also ist flush() bereits teil von flcose().

          gruß mod

          1. Hello,

            Das LOCK_UN  _musst_  Du dir schenken. Durch fclose() wird das File auch entsperrt, und zwar in einem abgeschlossenen Auftrag (atomarisiert).

            siehe Changelog: http://de.php.net/manual/de/function.flock.php
            seit php 5.3.2 muss wieder manuell entsperrt vor fclose()

            Das wäre ja ein fataler Konzeptfehler von PHP.
            Das kann und will ich erstmal gar nicht glauben. Wer denkt sich denn so einen Unsinn aus?

            Sonst müsstest Du der Ordnung halber vor dem Entsperren noch ein Flush() auslösen, damit ggf. benutzte Buffer vor dem Entsperren noch in Richtung Datei geleert werden.

            gut, also ist flush() bereits teil von flcose().

            Das hat das Betriebssystem sicherzustellen, ganauso, wie das entsperren.

            Haben die von PHP also einen Teil der jeweiligen Betriebssysteme ausgetauscht?

            Wenn das stimmt, was Du schreibst, werde ich PHP nicht mehr benutzten und auch jedem Anderen davon abraten!

            Liebe Grüße aus dem schönen Oberharz

            Tom vom Berg

            --
             ☻_
            /▌
            / \ Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
            1. Hello,

              Wenn das stimmt, was Du schreibst, werde ich PHP nicht mehr benutzten und auch jedem Anderen davon abraten!

              Jeztt habe ich diese zwei Sätze dort gelesen und mich fragen, ob die noch richtig ticken bei PHP:

              siehe http://de.php.net/manual/de/function.flock.php

              1.
              On versions of PHP before 5.3.2, the lock is released also by fclose() (which is also called automatically when script finished).

              2.
              Version Description
              5.3.2 The automatic unlocking when the file's resource handle is closed was removed. Unlocking now always has to be done manually.

              Und jetzt SCHREIE ich mal ganz laut HILFE und hoffe, dass Christian Seiler, der sich mit den Interna sehr gut auskennt, überhaupt noch mitliest hier und mir erklären kann, was der Unfug soll.

              Liebe Grüße aus dem schönen Oberharz

              Tom vom Berg

              --
               ☻_
              /▌
              / \ Nur selber lernen macht schlau
              http://bergpost.annerschbarrich.de
              1. Hello,

                Wenn das stimmt, was Du schreibst, werde ich PHP nicht mehr benutzten und auch jedem Anderen davon abraten!

                Dann müssen die Entwickler verrückt geworden sein!

                PHP hat über sein Variablen-Konzept lange daran gebastelt, lost Handles zu vermeiden.
                Wenn ich aber jetzt nicht mehr gewähleisten kann, dass mit einem fclose() auch die Locks loswerde, dann passiert das Grausamste, was man sich für Shared Uses überhaupt nur vorstellen kann: es stehen Lost Locks.

                Ich kann mir das wirklich nicht vorstellen, dass die so dämlich sind. Da müssen sie einen Trick kennen, den ich noch gerne lernen würde!

                Jeztt habe ich diese zwei Sätze dort gelesen und mich fragen, ob die noch richtig ticken bei PHP:

                siehe http://de.php.net/manual/de/function.flock.php

                On versions of PHP before 5.3.2, the lock is released also by fclose() (which is also called automatically when script finished).

                Version Description
                5.3.2 The automatic unlocking when the file's resource handle is closed was removed. Unlocking now always has to be done manually.

                Und jetzt SCHREIE ich mal ganz laut HILFE und hoffe, dass Christian Seiler, der sich mit den Interna sehr gut auskennt, überhaupt noch mitliest hier und mir erklären kann, was der Unfug soll.

                Bitte Christian hilf mir aus diesem tiefen Trauertal und erklär mir einfach den Trick

                Liebe Grüße aus dem schönen Oberharz

                Tom vom Berg

                --
                 ☻_
                /▌
                / \ Nur selber lernen macht schlau
                http://bergpost.annerschbarrich.de
                1. Hallo,

                  [...] das Grausamste, was man sich für Shared Uses überhaupt nur vorstellen kann: es stehen Lost Locks.

                  das wäre in der Tat übel - aber andererseits erwarte ich von einem *Betriebssystem*, dass es Locks automatisch aufräumt, wenn das zugehörige File-Handle geschlossen wird. Dann wäre das nämlich alles egal, und das Problem wäre gar keins.

                  Daher ist für mich die Kernfrage: Erlaubt das OS (Linux, Windows) persistente Locks, nachdem die Datei geschlossen wurde? Und wenn ja, dann wäre das IMHO ein Punkt, an dem die OS-Entwickler dringendst nachbessern müssten.
                  Andererseits: Wenn dieses Problem auf OS-Ebene wirklich existierte, dann hätte es wohl schon so oft zu Problemen geführt, dass wir das wüssten.

                  Ciao,
                   Martin

                  --
                  Paradox ist, wenn jemand eingefleischter Vegetarier ist.
                  Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
                  1. Hello,

                    Daher ist für mich die Kernfrage: Erlaubt das OS (Linux, Windows) persistente Locks, nachdem die Datei geschlossen wurde?

                    Das dürfte mMn nicht das Problem sein. Das Problem könnte nur sein, dass irgendein Willi meine Meckerei von anno Dunnemals nochmals (ohne die Hintergrundkenntnisse) aufgegriffen hat und nun das Locking aus dem OS ins PHP rein geholt hätte.

                    Der Hintergrund ist, dass die Harmonisierung zwiwschen DIO (Mandatory Locking) und FLOCK (Advisory Locking) in PHP nicht optimal war. DIO hat, wenn machbar und benutzt, Vorrang. Das wäre auch möglich gewesen. Es ging hier um Fehler beim LOCK_NB. Wenn schon ein DIO-LOCK-Besteht, darf ich keinen kann FLOCK mehr beantragen können, bzw. der FLOCK-LOCK_NB muss mit Fehler abgestraft werden, also scheitern. Das war nicht so. Ich hatte das seinerzeit bemänglet und von den Nasehochspezialisten eine rüde Absage erteilt bekommen.

                    Man kann aber auch in einem DIO-Umfeld ein Advisory Locking emulieren, nur nicht umgekehrt. Und man muss (heute) in einem Advisory-Locking das Umfeld beachten, ob es auch DIO (mandatory Locking) benutzt.

                    Und nun fürchte ich, dass meine als Scharlatanerie abgetane aber durchaus substantiierte Meckerei von anno Knölf jetzt nochmal bei jemandem aufgelaufen ist, der nicht so genau hingeschaut hat...

                    Liebe Grüße aus dem schönen Oberharz

                    Tom vom Berg

                    --
                     ☻_
                    /▌
                    / \ Nur selber lernen macht schlau
                    http://bergpost.annerschbarrich.de
                    1. Und nun fürchte ich, dass meine als Scharlatanerie abgetane aber durchaus substantiierte Meckerei von anno Knölf jetzt nochmal bei jemandem aufgelaufen ist, der nicht so genau hingeschaut hat...

                      LOL! Ich mag Menschen, die sich nicht nicht so wichtig nehmen ;-)

          2. Moin!

            Das LOCK_UN  _musst_  Du dir schenken. Durch fclose() wird das File auch entsperrt, und zwar in einem abgeschlossenen Auftrag (atomarisiert).

            siehe Changelog: http://de.php.net/manual/de/function.flock.php
            seit php 5.3.2 muss wieder manuell entsperrt vor fclose()

            Das ist ein Doku-Fehler.

            fclose() ruft nicht mehr explizit flock() vor dem Schließen auf. Denn das war ein Problem, weil es sich dadurch genauso verhalten hat, wie das manuell durch den User freigegebene Lock.

            Jetzt nutzt fclose() das Freigeben des Locks durch das Betriebssystem.

            - Sven Rautenberg

            1. Hello Sven,

              jetzt musst Du aber einen Stein fallen hören haben...

              Hast Du den Doku-Fehler gemeldet?

              Das ist ein Doku-Fehler.
              Jetzt nutzt fclose() das Freigeben des Locks durch das Betriebssystem.

              Liebe Grüße aus dem schönen Oberharz

              Tom vom Berg

              --
               ☻_
              /▌
              / \ Nur selber lernen macht schlau
              http://bergpost.annerschbarrich.de
              1. Moin!

                jetzt musst Du aber einen Stein fallen hören haben...

                Hast Du den Doku-Fehler gemeldet?

                Man kann diskutieren, ob das Entsperren der Datei (bzw. von Bereichen einer Datei - Filelocking geht ja zumindest OS-seitig auch auf Byteranges) explizit stattfinden sollte.

                Grundsätzlich muss man es ja irgendwie hinkriegen, eine Datei zu öffnen, ein Lock zu erhalten, dann Änderungen in die Datei zu schreiben und vor Lösen des Locks diese Änderungen zu persistieren. Und die Lösung ist recht simpel: fflush() schreibt alle Puffer auf die Platte. Danach kann man problemlos flock(LOCK_UN) aufrufen und fclose().

                Der Grund, warum man das manuell machen will: Manche Betriebssysteme lösen das Lock nicht sofort beim fclose(), sondern ggf. später beim Aufräumen geschlossener Filehandles - Windows wäre so ein Kandidat. Insofern kann man sich ohne explizite Lock-Freigabe schön ins Knie schießen.

                Weiterhin ist genau das vermutlich auch der Grund, warum das Freigeben des Locks bei fclose() rausgeflogen ist: Im Prinzip schreibt das Dateischließen alle Puffer auf die Platte. Die PHP-Implementierung hat aber im Prinzip innerhalb der Funktion fclose() einfach nur flock(LOCK_UN) aufgerufen, und erst danach wäre durch Dateischließen das Flush implizit erfolgt. Mithin besteht also die Chance, dass durch diese Race-Condition Dateiinhalt verloren geht.

                Insofern sollte man festhalten:

                fopen();  
                flock(LOCK_EX);  
                  
                fwrite();  
                  
                fflush();  
                flock(LOCK_UN);  
                fclose();  
                
                

                - Sven Rautenberg

                1. Hello,

                  Weiterhin ist genau das vermutlich auch der Grund, warum das Freigeben des Locks bei fclose() rausgeflogen ist: Im Prinzip schreibt das Dateischließen alle Puffer auf die Platte. Die PHP-Implementierung hat aber im Prinzip innerhalb der Funktion fclose() einfach nur flock(LOCK_UN) aufgerufen, und erst danach wäre durch Dateischließen das Flush implizit erfolgt. Mithin besteht also die Chance, dass durch diese Race-Condition Dateiinhalt verloren geht.

                  Das ist mir klar gewesen, nicht aber, dass PHP in der (äußeren)-fclose()-Funktion noch nein flock() eingabut hatte, bevor dann endlich das fclose() des OS aufgerufen wurde...

                  Welches Windows meinst Du übrigens, dass die Locks erst (sehr viel später?) nach Rückgabe des Dateihandles beseitigen soll?

                  Insofern sollte man festhalten:

                  fopen();

                  flock(LOCK_EX);

                  fwrite();

                  fflush();
                  flock(LOCK_UN);
                  fclose();

                    
                  Das wäre eine durchaus als sicher vorstellbarere, wenn auch umständliche Vorgehensweise. Da stimme ich Dir zu.  
                    
                    
                    
                  Liebe Grüße aus dem schönen Oberharz  
                    
                    
                  Tom vom Berg  
                  ![](http://selfhtml.bitworks.de/Virencheck.gif)  
                    
                  
                  -- 
                   ☻\_  
                  /▌  
                  / \ Nur selber lernen macht schlau  
                  <http://bergpost.annerschbarrich.de>
                  
                  1. Moin!

                    Welches Windows meinst Du übrigens, dass die Locks erst (sehr viel später?) nach Rückgabe des Dateihandles beseitigen soll?

                    Steht so in der Doku zur Funktion LockFileEx.

                    - Sven Rautenberg

  3. Hi,

    es geht konkret um die in ein ErrorHandling-Objekt implemetierte Errorlog-Datei für schwerwiegende Fehler gehen. Es geht um die Problematik, wenn 2 oder mehrere Script gleichzeitig auf error.log zu greifen.

    Funktion error_log verwenden, PHP sich drum kümmern lassen, fertig.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?