AllesMeins: Datei richtig gesperrt/PHP verhalten bei flock()

Hiho,

folgendes Problem. Ich habe folgendes Script geschrieben um das rückwärts runterzählen eines Wertes zu realisieren:

$fp = fopen("datei","r+");
flock($fp, LOCK_EX);
$count = chop(fread($fp, 1024));

$count--;
if($count == 0){
   //Führe Aktionen aus, setzte neuen Startwert
[...]
   $count = mt_rand($min,$max);
} elseif($count < 0){
   echo "Fehlerhafte Anzahl!";
}
rewind($fp);
ftruncate($fp,0);
fwrite($fp, $count);
fclose($fp);

Das hat auch einoige Monate wunderbar und problemlos funktioniert. Doch in den letzten Tagen stelle ich immer wieder fest, dass - ohne ersichtbaren Grund - plötzlich negative Zahlen in der Datei auftauchen. Das geschieht auch wenn die Zahl eigentlich noch weit entfernt ist von 0 (so im Größenbereich von 12000 oder so) und scheinbar scheint er auch die 0 zu überspringen, denn die Aktionen die eigentlich bei 0 passieren treten auch nicht auf.

Das ganze ist mir ein ziemliches Rätsel. Irgend eine Idee woran das liegen könnte? Oder andere Vorschlkäge wie ich dieses Zählscript stabiler bekommen könnte?

Grüße

Marc

  1. Hi AllesMeins,

    flock($fp, LOCK_EX);

    An dieser Stelle prüfst du ja gar nicht, ob das Locking erfolgreich war!! Die Funktion flock() versucht normalerweise mehrfach hintereinander die Datei zu sperren, ist dies nicht möglich, so gibt es als Rückgabewert false und das Script arbeitet weiter.

    Um genauer kontrollieren zu können, wie flock() arbeitet, solltest du das Non-Blocking aktivieren und flock mehrfach hintereinander bis die Datei gesperrt ist:

    $lock = false;  
    // Bis zu 5 mal versuchen die Datei zu sperren  
    for($i = 0; $i < 5; $i ++) {  
      // LOCK_NB verhindert, dass flock selber es mehrfach versucht  
      $lock = flock($fp, LOCK_EX + LOCK_NB);  
      // Im Erfolgsfall Schleife abbrechen  
      if($lock) break;  
      // Ansonsten 8 Milli-Sekunden warten und erneut probieren  
      [link:http://php.nt/usleep@title=usleep](8000);  
    }  
      
    // Prüfung von $lock  
    if(!$lock) {  
      // Fehlerausgabe, Datei konnte nicht gesperrt werden  
    }  
    else {  
      // weiter mit der Datei arbeiten  
    }
    

    MfG, Dennis.

    1. Hiho,

      danke für die Antwort. Wie genau arbeitet flock da? Also klassischer Fall, die Datei ist bereits gesperrt. Eine zweite Instanz des Scriptes ruft flock() auf. Gibt flock() dann einfach 'false' zurück oder wartet es. Bzw. sollte nicht schon fopen() warten, wenn die Datei gesperrt ist?

      Grüße

      Marc

      1. Hi AllesMeins,

        Wie genau arbeitet flock da?

        Wie flock() genau arbeitet weiß ich nicht - ich meine lediglich zu wissen, dass es unter Windows nicht, oder nicht richtig arbeitet.

        Also klassischer Fall, die Datei ist bereits gesperrt. Eine zweite Instanz des Scriptes ruft flock() auf. Gibt flock() dann einfach 'false' zurück oder wartet es. Bzw. sollte nicht schon fopen() warten, wenn die Datei gesperrt ist?

        fopen() wartet überhaupt nicht, fopen() macht gnadenlos das was es soll, es öffnet die Datei. Wenn du dann mit fwrite darin herumarbeitest, dann wird das auch gemacht - fertig. Die Aufgabe des Programmieres ist es vor irgendwelchen Aktionen mit der Datei diese zu sperren, das macht flock(). flock() gibt true zurück, wenn das gemacht werden konnte, was sollte (Datei für Lese oder Schreibzugriff sperren) oder false, wenn das nicht gelungen ist. Mit LOCK_NB sollte direkt false zurück gegeben werden, ohne LOCK_NB probiert flock() es soweit ich weiß mehrfach.

        MfG, Dennis.

        1. Hiho,

          Wie flock() genau arbeitet weiß ich nicht - ich meine lediglich zu wissen, dass es unter Windows nicht, oder nicht richtig arbeitet.

          Doch, soll es angeblich tun: "flock() allows you to perform a simple reader/writer model which can be used on virtually every platform (including most Unix derivatives and even Windows)." - aber hier geht es ja sowieso um ein Linux-System.

          Mit LOCK_NB sollte direkt false zurück gegeben werden, ohne
          LOCK_NB probiert flock() es soweit ich weiß mehrfach.

          Aber genau das soll es doch - es mehrfach probieren. Warum soll ich dann so ein Konstrukt drumbauen, das mehrfache probieren abschalten und dann per Hand mehrfach probieren lassen?

          Grüße

          Marc

          1. Hi AllesMeins,

            Aber genau das soll es doch - es mehrfach probieren. Warum soll ich dann so ein Konstrukt drumbauen, das mehrfache probieren abschalten und dann per Hand mehrfach probieren lassen?

            Weil die PHP 4er Version auf der ich das zuletzt ausprobiert habe dann unendlich lange gewartet hat und auf Teufel komm raus probiert hat die Datei zu sperren, wohingegen ich es lediglich 5 mal probieren und dann aufgeben wollte.

            Möglicherweise war das aber auch nur ein mittlerweile gefixter Bug - da müsste man sich wohl mal kundig machen.

            MfG, Dennis.

            1. Hiho,

              Weil die PHP 4er Version auf der ich das zuletzt ausprobiert habe dann unendlich lange gewartet hat und auf Teufel komm raus probiert hat die Datei zu sperren,

              hmm, dann sollte mein Entwurf die Datei aber doch eigentlich zuverlässig sperren, wenn flock() ansonsten wartet bis sie gesperrt ist. Klar, zu testen ob es geklappt hat kann nie schaden. Aber trotzdem kann das dann ja eigentlich nicht das Problem sein, das ich beobachtet habe.

              Marc

              1. Hallo,

                hmm, dann sollte mein Entwurf die Datei aber doch eigentlich zuverlässig sperren, wenn flock() ansonsten wartet bis sie gesperrt ist. Klar, zu testen ob es geklappt hat kann nie schaden. Aber trotzdem kann das dann ja eigentlich nicht das Problem sein, das ich beobachtet habe.

                flock() arbeitet auf Multithreaded Server nicht und ich vermute da den eigentlichen Grund für das Fehlschagen des Scripts. Ich kann Dir nur eine unbequeme Möglichkeit aufzeigen, die allerdings hundertprozentig arbeitet: http://eddi.to-grip.de/PHP/semaphor/?0.2.1.php
                U. U. heißt das auch, PHP neu zu bauen.

                Gruß aus Berlin!
                eddi

                1. Hi,

                  U. U. heißt das auch, PHP neu zu bauen.

                  Inwiefern, bzw. unter welchen Umständen?

                  Gruß, Cybaer

                  --
                  Hinweis an Fragesteller: Fremde haben ihre Freizeit geopfert, um Dir zu helfen. Helfe Du auch im Archiv Suchenden: Beende deinen Thread mit einem "Hat geholfen" oder "Hat nicht geholfen"!
                  1. Hallo,

                    bei PHP sind Semaphoren standardmäßig nicht aktiviert.

                    Gruß aus Berlin!
                    eddi

                    1. Hi,

                      bei PHP sind Semaphoren standardmäßig nicht aktiviert.

                      Hmm, daß dem so ist, geht leider aus dem PHP-Manual AFAIK nicht hervor. Und bei *allen meinen* Providern ist es aktiviert. Habe ich Glück, oder ist es eher die absolute Ausnahme, daß das Modul nicht eingebunden ist?

                      Und ich vermute mal, daß ggf. ein function_exists('sem_get') dann, wie üblich, ein FALSE zurück gibt!?

                      Gruß, Cybaer

                      --
                      Hinweis an Fragesteller: Fremde haben ihre Freizeit geopfert, um Dir zu helfen. Helfe Du auch im Archiv Suchenden: Beende deinen Thread mit einem "Hat geholfen" oder "Hat nicht geholfen"!
                      1. Re:

                        bei PHP sind Semaphoren standardmäßig nicht aktiviert.

                        Hmm, daß dem so ist, geht leider aus dem PHP-Manual AFAIK nicht hervor.

                        Die englische Originalfassung ist wie immer exakter.

                        Und bei *allen meinen* Providern ist es aktiviert. Habe ich Glück, oder ist es eher die absolute Ausnahme, daß das Modul nicht eingebunden ist?

                        Wie Du es schreibst: _absolute Ausnahme_ !!!111ELF

                        Und ich vermute mal, daß ggf. ein function_exists('sem_get') dann, wie üblich, ein FALSE zurück gibt!?

                        Ja.

                        Gruß aus Berlin!
                        eddi

                        1. Hi,

                          Die englische Originalfassung ist wie immer exakter.

                          F*ck! >;->

                          Danke für den "Zaunpfahlwink". ;)

                          Wie Du es schreibst: _absolute Ausnahme_ !!!111ELF

                          Fein - aber auch so was will ja bedacht sein ... :-/

                          Gruß, Cybaer

                          --
                          Hinweis an Fragesteller: Fremde haben ihre Freizeit geopfert, um Dir zu helfen. Helfe Du auch im Archiv Suchenden: Beende deinen Thread mit einem "Hat geholfen" oder "Hat nicht geholfen"!
                2. Hiho,

                  flock() arbeitet auf Multithreaded Server nicht und ich vermute da den eigentlichen Grund für das Fehlschagen des Scripts.

                  Hmm, hab gerade mal ein (bzw. 2) Testscript gebaut.

                  test1.php:

                  <?php
                  $fp = fopen("test.stadb","r+");
                  echo time() . ": 1 geöffnet<br>";
                  flock($fp, LOCK_EX);
                  echo time() . ": 1 gesperrt<br>";
                  $count = chop(fread($fp, 1024));
                  echo time() . ": 1 gelesen - " . $count . "<br>";

                  sleep(10);

                  rewind($fp);
                  ftruncate($fp,0);
                  fwrite($fp, "von 1 geschrieben");
                  fclose($fp);

                  echo time() . ": 1 geschlossen<br>";
                  ?>

                  test2.php:
                  <?php
                  $fp = fopen("test.stadb","r+");
                  echo time() . ": 2 geöffnet<br>";
                  flock($fp, LOCK_EX);
                  echo time() . ": 2 gesperrt<br>";
                  $count = chop(fread($fp, 1024));

                  echo time() . ": 2 gelesen - " . $count . "<br>";

                  rewind($fp);
                  ftruncate($fp,0);
                  fwrite($fp, "geschrieben von zwei");
                  fclose($fp);

                  echo time() . ": 2 geschlossen<br>";
                  ?>

                  Dann die beiden Scripte nacheinander gestartet. Und wie gewünscht wartet test2.php so lange bis test1.php fertiggelaufen ist und die datei wieder schließt. Das sind die Ausgaben, die ich erhalte:

                  test1.php:
                  1146915771: 1 geöffnet
                  1146915771: 1 gesperrt
                  1146915771: 1 gelesen - von 1 geschrieben
                  1146915781: 1 geschlossen

                  test2.php:
                  1146915773: 2 geöffnet
                  1146915781: 2 gesperrt
                  1146915781: 2 gelesen - von 1 geschrieben
                  1146915781: 2 geschlossen

                  Ich denke, dass heisst doch das das flock() funktioniert, oder? Denn obwohl ich test2.php nur 2 Sekunden nach test1.php gestartet habe hat er bis zum Ende von test1 gewartet, bis er die Datei sperren konnte.

                  Ist an meiner Versuchsanordnung irgendwas nicht in Ordnung, oder habe ich sonst irgendwas falsch gemacht? Und falls nein: Irgendwelche anderen Ideen, wie es sein kann, dass plötzlich negative Zahlen von meinem Ausgangsscript produziert werden?

                  Oder kann es sein, das flock() nicht immer zuverlässig arbeitet?

                  Grüße

                  Marc

                  1. Hallo,

                    flock() arbeitet auf Multithreaded Server nicht...

                    Ich denke, dass heisst doch das das flock() funktioniert, oder? Denn obwohl ich test2.php nur 2 Sekunden nach test1.php gestartet habe hat er bis zum Ende von test1 gewartet, bis er die Datei sperren konnte.

                    na wenn Du Dich mit einem Test begnügen magst?! Es ist nicht wichtig, daß _ein_ Test gut geht. Tom und ich haben ihr schon Tests mit in die hunderttausende gehende Requestzahle durchgeführt.

                    Ist an meiner Versuchsanordnung irgendwas nicht in Ordnung, oder habe ich sonst irgendwas falsch gemacht? Und falls nein: Irgendwelche anderen Ideen, wie es sein kann, dass plötzlich negative Zahlen von meinem Ausgangsscript produziert werden?

                    Oder kann es sein, das flock() nicht immer zuverlässig arbeitet?

                    flock() arbeitet unter den selben Rahmenbedingenen immer gleich zuverlässig. Nur können die Rahmenbedingenen zwischen zwei Requests grundverschieden sein.

                    Welcher Server mit welchen Modulen und welcher Version kommt zum Einsatz?

                    Gruß aus Berlin!
                    eddi

                    1. Hiho,

                      na wenn Du Dich mit einem Test begnügen magst?!

                      Naja, eine solche Funktion sollte eigentlich stabil genug geschrieben sein, dass sie sich nicht von mehreren Anfragen aus dem Tritt bringen lässt. Ansonsten ist ihr Nutzwert nämlich gleich 0. Aber wenn du sagst, dass sie Funktion nicht stabil ist, dann bringen auch Tests mit 100.000 Requests nichts - dann ist sie einfach nicht Vertrauenswürdig und gehört verbessert oder rausgestrichen.

                      flock() arbeitet unter den selben Rahmenbedingenen immer gleich zuverlässig. Nur können die Rahmenbedingenen zwischen zwei Requests grundverschieden sein.

                      Welche Rahmenbedingungen wären das?

                      Welcher Server mit welchen Modulen und welcher Version kommt zum Einsatz?

                      Laut phpinfo():
                      Apache/1.3.26 (Linux/SuSE) PHP/4.4.2 mod_ssl/2.8.10 OpenSSL/0.9.6g FrontPage/5.0.2.2623

                      Reicht das oder soll ich noch mehr raussuchen?

                      Grüße

                      Marc

                      1. Hallo,

                        Naja, eine solche Funktion sollte eigentlich stabil genug geschrieben sein, dass sie sich nicht von mehreren Anfragen aus dem Tritt bringen lässt. Ansonsten ist ihr Nutzwert nämlich gleich 0. Aber wenn du sagst, dass sie Funktion nicht stabil ist, dann bringen auch Tests mit 100.000 Requests nichts - dann ist sie einfach nicht Vertrauenswürdig und gehört verbessert oder rausgestrichen.

                        was hast Du an dem dick eingerahmten Hinweis mit der Überschrift WARNUNG in der Beschreibung der Funktion flock des Manuals nicht verstanden?

                        Laut phpinfo():
                        Apache/1.3.26 (Linux/SuSE) PHP/4.4.2 mod_ssl/2.8.10 OpenSSL/0.9.6g FrontPage/5.0.2.2623

                        Reicht das oder soll ich noch mehr raussuchen?

                        Es sollte mit dem Apachen 1.3 keinerlei Probleme geben (mal abgesehen davon, daß Dein Server dringend nach Updates schreit) aber bei einer vermuteten SuSE 8.x ist alles möglich.

                        Gruß aus Berlin!
                        eddi

    2. Hello,

      flock($fp, LOCK_EX);

      An dieser Stelle prüfst du ja gar nicht, ob das Locking erfolgreich war!! Die Funktion flock() versucht normalerweise mehrfach hintereinander die Datei zu sperren, ist dies nicht möglich, so gibt es als Rückgabewert false und das Script arbeitet weiter.

      *ähähäh* :-)

      flock() kennt zwei Modi:
      Blocking Mode
        PHP fordert die Sperre beim OS an.
        Das wird soalnge versucht, bis Erfolg eintritt
        Die Kontrolle wird nicht vorher an das laufende Programm zurückgegeben
        Wenn es zu lange dauert, greift PHPs Timeout-Mechanismus

      Nonblocking Mode
        Es wird von PHP die Sperre beim OS angefordert.
        Dieses versucht das i.d.R. einige Male in kurzen Abständen hintereinander
        Misslingt dies, wird die Kontrolle mit einem Fehlerwert an das laufende
        Programm zurückgegeben

      Default ist bei PHP der Blocking Mode

      Harzliche Grüße vom Berg
      http://www.annerschbarrich.de

      Tom

      --
      Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
      Nur selber lernen macht schlau