flock vorhanden, aber Datei schon 2x gelöscht ?
xNeTworKx
- perl
Hallo,
Jetzt ist es mir schon zum 2. Mal passiert, daß meine Statistik gelöscht wurde, und wieder von 0 zum zählen angefangen hat, obwohl ich flock verwende. was kann noch die Ursache sein, daß so etwas passiert ?
Hier das Script, das pro Klick, die Statistik um 1 erhöht.
#!/usr/bin/perl -w
use CGI;
use strict;
my $query = new CGI;
my $var = $query->param('var');
my $ip = $ENV{'REMOTE_ADDR'};
my $file;
my($neues,
$uebermich,
$fotos,
$musik,
$statistiken,
$forum,
$seiteninfo,
$acid4uamp) = (0,0,0,0,0,0,0,0);
if ($ip ne '212.17.119.105') {
open (FILE,"clicks.txt") or die "Cant open database, please try again: $!\n";
while (<FILE>) {
$file .= $_;
}
close FILE;
if ($file =~ /<neues>(\d+?)</neues>/) { $neues = $1; }
if ($file =~ /<uebermich>(\d+?)</uebermich>/) { $uebermich = $1; }
if ($file =~ /<fotos>(\d+?)</fotos>/) { $fotos = $1; }
if ($file =~ /<musik>(\d+?)</musik>/) { $musik = $1; }
if ($file =~ /<statistiken>(\d+?)</statistiken>/) { $statistiken = $1; }
if ($file =~ /<forum>(\d+?)</forum>/) { $forum = $1; }
if ($file =~ /<seiteninfo>(\d+?)</seiteninfo>/) { $seiteninfo = $1; }
if ($file =~ /<acid4uamp>(\d+?)</acid4uamp>/) { $acid4uamp = $1; }
if ($var eq 'a') { $neues++; }
if ($var eq 'b') { $uebermich++; }
if ($var eq 'c') { $fotos++; }
if ($var eq 'd') { $musik++; }
if ($var eq 'e') { $statistiken++; }
if ($var eq 'f') { $forum++; }
if ($var eq 'g') { $seiteninfo++; }
if ($var eq 'i') { $acid4uamp++; }
open (FILE,">clicks.txt") or die "Cant save to database : $!\n";
flock FILE, 2;
print FILE "<neues>$neues</neues>\n";
print FILE "<uebermich>$uebermich</uebermich>\n";
print FILE "<fotos>$fotos</fotos>\n";
print FILE "<musik>$musik</musik>\n";
print FILE "<statistiken>$statistiken</statistiken>\n";
print FILE "<forum>$forum</forum>\n";
print FILE "<seiteninfo>$seiteninfo</seiteninfo>\n";
print FILE "<acid4uamp>$acid4uamp</acid4uamp>\n";
close FILE;
}
if ($var eq 'a') {
print $query->redirect('http://www.acid4u.com/homepage4/news/news.cgi');
}
if ($var eq 'b') {
print $query->redirect('http://www.acid4u.com/homepage4/uebermich.html');
}
if ($var eq 'c') {
print $query->redirect('http://www.acid4u.com/homepage4/fotos/fotos.html');
}
if ($var eq 'd') {
print $query->redirect('http://www.acid4u.com/homepage4/musik/musik_abfrage.cgi');
}
if ($var eq 'e') {
print $query->redirect('http://www.acid4u.com/homepage4/statistik.cgi');
}
if ($var eq 'f') {
print $query->redirect('http://www.acid4u.com/homepage4/forum/forum.cgi');
}
if ($var eq 'g') {
print $query->redirect('http://www.acid4u.com/homepage4/ueberseite.html');
}
if ($var eq 'i') {
print $query->redirect('http://www.acid4u.com/homepage4/acid4uamp/acid4uamp.shtml');
}
Hallo,
hm...
entweder der flock-Mechanismus ist nicht richtig implementiert auf Deinem WebServer oder es giebt ein weiteres Script, welches sich nicht um ein lock kümmert. flock verhindert nur einen Zugriff wenn es von ALLEN genutzt wird. Ein Script, welches die Datei zum Schreiben öffnet, und danach nicht per flock den "Status" abfragt, überschreibt hemmungslos die Datei.
Eine andere Unsauberkeit enthält Dein Script: Was passiert, wenn das Script quasi gleichzeitig gestartet wird?
[...]
open (FILE,"clicks.txt") or die "Cant open database, please try again: $!\n";
while (<FILE>) {
$file .= $_;
}
close FILE;
[...]
Beide lesen den Inhalt der Datei und "merken" sich den. Beide "arbeiten" weiter, der erste öffnet die Datei zum Schreiben und schreibt, der 2te öffnet auch, bleibt am flock() stehen, bis der erste das lock wieder aufhebt und schreibt dann - Und zwar u.U. genau den gleichen Inhalt! So gehen Die Zähler verloren.
open (FILE,">clicks.txt") or die "Cant save to database : $!\n";
flock FILE, 2;
[...]
close FILE;
Lösung: Entweder baust Du um Deine Struktur noch mittels Hilfsdatei und flock für diese Datei ein "Lock" 'drumrum oder Du öffnest die Datei gleich zum Lesen und schreiben, Lockst, liest, berechnest den neuen Inhalt, löscht den alten Inhalt, schreibst und schließt dann wieder die Datei.
Gruß Frank
P.S. mir kommt gerade noch 'ne Idee: kann es sein, daß Dein Script - warum auch immer - ab und zu z.B. nach dem Öffnen der Datei zum Schreiben abgeschossen wird?
Hi!
Szenario: Zwei Besucher rufen eine Seite auf, zwei Instanzen des Counters werden gestartet; seien ihre Namen A und B. A oeffnet die Datei, liest sie ein, schliesst sie, zaehlt, was zu zaehlen ist, oeffnet die Datei zum Schreiben - diese wird damit auf Nullgroesse zurueckgesetzt. In diesem Moment schaltet das multitaskende Betriebssystem zu Prozess B. Der oeffnet die Datei zum Lesen, liest die gesamten Null Bytes ein, ... den Rest kannst Du Dir denken.
Und wenn Du nach dem open zum Lesen auch flock benutzen wuerdest? Das verringert die Wahrscheinlichkeit von Konflikten, ja, aber vollstaendig vermeiden wuerde es diese immer noch nicht. Das geht naemlich nicht. Nicht, solange Datei lesen und Datei schreiben in Deinem Script zwei voellig voneinander unabhaengige Dinge sind.
Du hast zwei Moeglichkeiten:
1. Oeffne die Datei mit
open(FILE, "+< clicks.txt");
zum gleichzeitigen Lesen und schreiben, sperre sie mit flock(), lies sie wie gewoehnlich ein, schliesse sie NICHT, sondern setze mit
seek(FILE, 0, 0);
den Dateizeiger an den Anfang zurueck, schreibe alles was Du loswerden willst. Jetzt muss Du die Datei abschneiden, denn wenn der neue Inhalt kuerzer ist als der alte, bleibt das, was hinter dem jetzigen Dateizeiger war, auch weiterhin stehen. Also
truncate(FILE, tell(FILE)); # immer schoen perlfunc lesen ;-)
und jetzt endlich die Datei schliessen.
Auf diese Weise hast Du Lesen und Schreiben zu einer einzigen Operation verknuepft, die gesamtheitlich durch flock() beschuetzt wird. Ich habe das auch mal in dieser Weise gemacht. Das Script laeuft noch heute, hatte auch noch nie Probleme gemacht, allerdings war es auch noch nie "unter Sperrfeuer", musste sich also noch nie unter erheblicher Last bewaehren.
2. Du packst saemtliche kritischen Operationen in einen schwarzen Kasten, der nur eine Tuer hat, und stellst einen Tuersteher davor. Der sorgt dafuer, dass immer nur einer in den Kasten darf und weitere Eintrittswillige gefaelligst zu warten haben. Im Programmierhandwerk wird so ein Tuersteher als binaere Semaphore bezeichnet, aber anderes als der Name suggeriert ist das eigentlich alles ganz einfach.
So eine Semaphore bietet Dir entweder das Betriebssystem an, wobei sich hier nicht viel gutes ueber die Portabilitaet sagen laesst. Oder Du nimmst ein Modul, dass sowas mit lustigen Tricks plattformuebergreifend bewerkstelligt. Da fallen mir auf Anhieb zwei solche Module ein. Eines, welches Teil des Sourcecodes dieses Forums ist (http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/selfforum/selfforum-cgi/shared/ (Lock.pm und Unterverzeichnis Lock)) und eines, das ich selber mal geschrieben habe (http://calocybe.dyndns.org/temp/FileSemaphore/). Ersteres ist ziemlich umfangreich und scheint ziemlich viel zu koennen. Zweiteres erfuellt seinen Zweck auch. ;-)
while (<FILE>) {
$file .= $_;
}
Einfacher:
{
local $/;
$file = <FILE>;
}
flock FILE, 2;
^ Wuerdest Du die LOCK_* Konstanten verwenden (mit use Fcntl ':flock'; importieren), wuesste man, was das bedeuted, ohne erst in der Referenz nachzuschauen.
HTH && So long
--
Falscher oder fehlender Kaffee. Benutzer angehalten.
[calokey: perl flock semaphore]
Hi!
Auf genau diese Verfahren wollte ich auch hinaus, nur mit SELFdenken. Auch ich benutze es seit einiger Zeit problemlos, auch schon unter richtiger Last.
open(FILE, "+< clicks.txt");
zum gleichzeitigen Lesen und schreiben, sperre sie mit flock(), lies sie wie gewoehnlich ein, schliesse sie NICHT, sondern setze mit
seek(FILE, 0, 0);
den Dateizeiger an den Anfang zurueck, schreibe alles was Du loswerden willst. Jetzt muss Du die Datei abschneiden, denn wenn der neue Inhalt kuerzer ist als der alte, bleibt das, was hinter dem jetzigen Dateizeiger war, auch weiterhin stehen. Also
truncate(FILE, tell(FILE)); # immer schoen perlfunc lesen ;-)
und jetzt endlich die Datei schliessen.
Oder :
[...]
seek (FILE,0,0);
truncate(FILE, 0);
print FILE, "stuff";
close FILE;
flock FILE, 2;
^ Wuerdest Du die LOCK_* Konstanten verwenden (mit use Fcntl ':flock'; importieren), wuesste man, was das bedeuted, ohne erst in der Referenz nachzuschauen.
das beruhigt - ich musste auch erstmal nachlesen, sowas hat ja keiner in Kopf - oder?
Gruß Frank
Hallo,
Auf genau diese Verfahren wollte ich auch hinaus, nur mit SELFdenken.
ich hab eigentlich nichts dagegen selbst nachzudenken, aber ehrlich gesagt wäre ich auf diese Lösung nie gekommen, da mir seek eigentlich bis dato nichts sagte, und bei truncate wusst ich auch nur, daß es die ganze Datei löscht, aber nicht auf allen Servern funktioniert ( warum eigentlich ? ), deswegen bin ich froh, daß mir Calocybe ein Beispiel gab, woran ich mich in Zukunft orientieren kann, und daß mir sicher wieder neue Perspektiven öffnet.
Moin moin!
Auf genau diese Verfahren wollte ich auch hinaus, nur mit SELFdenken.
Ja, war gerade mal so in der Stimmung, passiert nicht mehr so oft in letzter Zeit. Und das ausgerechnet heute, wo fett die Sonne scheint! *g* Aber hab bei der Gelegenheit mir gleich ausgedacht, den Beitrag mit solchen Keywords zu markieren, damit ich spaeter mal schnell wieder drauf verweisen kann.
Auch ich benutze es seit einiger Zeit problemlos, auch schon unter richtiger Last.
Gut zu wissen.
das beruhigt - ich musste auch erstmal nachlesen, sowas hat ja keiner in Kopf - oder?
Jedenfalls kein ernstzunehmender Programmierer. ;-)
xNeTworKx: local: Wert der Variable sichern und am Ende des einschliessenden Blocks automatisch wiederherstellen. Ausserdem wird die Variable gleich auf undef gesetzt. Genau das soll mit $/ passieren, dann wird naemlich die gesamte Datei in einem Stueck eingelesen, also nicht mehr zeilenweise. (Genauer: Die gesamte Datei wird als eine einzige Zeile betrachtet. Siehe dazu perlvar (INPUT_RECORD_SEPARATOR))
So long
--
Discovering the usefulness of the "command.com" shell on Windows 9x is left as an exercise to the reader :)
-- from Perl's README.win32 file
Hi,
xNeTworKx: local: Wert der Variable sichern und am Ende des einschliessenden Blocks automatisch wiederherstellen. Ausserdem wird die Variable gleich auf undef gesetzt. Genau das soll mit $/ passieren, dann wird naemlich die gesamte Datei in einem Stueck eingelesen, also nicht mehr zeilenweise. (Genauer: Die gesamte Datei wird als eine einzige Zeile betrachtet. Siehe dazu perlvar (INPUT_RECORD_SEPARATOR))
Aha, ich habe bis jetzt auch immer in eine Variable eingelesen, und nicht zeilenweise :
while (<FILE>) {
$in .= $_;
}
Ist denn die Lösung mit local $/ so viel effizienter ?
Aha, ich habe bis jetzt auch immer in eine Variable eingelesen, und nicht zeilenweise :
while (<FILE>) {
$in .= $_;
}
Das ist ja eben doch zeilenweise eingelesen und hinterher (bzw. waehrenddessen) wieder zusammengebaut.
Ist denn die Lösung mit local $/ so viel effizienter ?
Wieviel weiss ich nicht, aber effizienter auf jeden Fall. Beim zeilenweisen Einlesen muss Perl ja erstmal die ganzen Zeilenenden suchen, beim Anhaengen an $in wird staendig der allokierte Speicherbereich vergroessert, und einige Sachen mehr, die alle "behind the scenes" ablaufen. Das ist der Nachteil an Scriptsprachen, man sieht nicht, wie aufwendig die Dinge in Wirklichkeit sind.
So long
--
Falscher oder fehlender Kaffee. Benutzer angehalten.
Hi nochmal,
wenn ich jetzt aber gleichzeitig lese und schreibe hab ich ein kleines Problem. Wenn das File nicht exisitert, bekomme ich No such file or directory. Vorher habe ich es immer beim Lesen zuerst abgefragt, ob es überhaupt exisitert. Das ging leicht mit einer if(-e Schleife, aber wie kann ich das nun jetzt am Besten machen ?
Sorry was vergessen,
natürlich könnte ich
if (-e 'neuebesucher.txt') {
open (FILE,'+<neuebesucher.txt') or die "Cant open database of visitors : $!\n";
} else {
open (FILE,'>neuebesucher.txt') or die "Cant open database of visitors : $!\n";
}
schreiben, aber gibts da nichts eleganteres ?
Moin!
wenn ich jetzt aber gleichzeitig lese und schreibe hab ich ein kleines Problem. Wenn das File nicht exisitert, bekomme ich No such file or directory. Vorher habe ich es immer beim Lesen zuerst abgefragt, ob es überhaupt exisitert. Das ging leicht mit einer if(-e Schleife, aber wie kann ich das nun jetzt am Besten machen ?
Verstehe ich nicht. Wieso sollst Du das jetzt nicht mehr machen koennen?
Aber vielleicht hilft Dir sowas:
if (open(FILE, '+< ...')) {
# datei existiert
# flock
# auslesen und zaehlen
# seek(FILE, 0, 0)
} else {
# datei existiert nicht
# alle werte auf default (0) zuruecksetzen
open(FILE, '> ...') || [fehlerbehandlung];
# flock
}
So long
--
The differences between theory and practice are smaller in theory than they are in practice.
Hallo,
danke für die ausführliche Antwort, ich habe es gleich bei meiner neuen Seite, die ich gerade mache sohin geändert.
while (<FILE>) {
$file .= $_;
}
Einfacher:
local $/;
$file = <FILE>;
Das hätte ich gerne ein weniger genauer erklärt. was bedeutet $/, und wie arbeitet dieses local damit ?
flock FILE, 2;
^ Wuerdest Du die LOCK_* Konstanten verwenden (mit use Fcntl ':flock'; importieren), wuesste man, was das bedeuted, ohne erst in der Referenz nachzuschauen.
Hmmm, werds mir mal genauer ansehen.
Hallo,
while (<FILE>) {
$file .= $_;
}
Einfacher:
local $/;
$file = <FILE>;
Das hätte ich gerne ein weniger genauer erklärt. was bedeutet $/,
perldoc perlvar:
$RS
$/ The input record separator, newline by default.
This is used to influence Perl's idea of what a
"line" is. Works like awk's RS variable,
including treating empty lines as delimiters if
set to the null string. [...]
und wie arbeitet dieses local damit ?
'local' packt den Inhalt der Variablen auf den Stack und setzt sie auf 'undef'.
Im Gegensatz zu 'my', die eine Variable in einem Gueltigkeitsbereich definiert.
Deshalb ist folgendes mit local, aber nicht mit my moeglich:
ckruse@www:~/tests/httpd-2.0.36$ cat test.pl
#!/usr/bin/perl -w
use strict;
use vars qw($testvar);
$testvar = 10;
doit();
doit_one();
sub doit() {
local $testvar = 100;
print "doit(): ",$testvar,"\n";
doit_one();
}
sub doit_one() {
print "doit_one():",$testvar,"\n";
}
ckruse@www:~/tests/httpd-2.0.36$ perl test.pl
doit(): 100
doit_one():100
doit_one():10
ckruse@www:~/tests/httpd-2.0.36$ vim test.pl
[...]
ckruse@www:~/tests/httpd-2.0.36$ cat test.pl
#!/usr/bin/perl -w
use strict;
use vars qw($testvar);
$testvar = 10;
doit();
doit_one();
sub doit() {
my $testvar = 100;
print "doit(): ",$testvar,"\n";
doit_one();
}
sub doit_one() {
print "doit_one():",$testvar,"\n";
}
ckruse@www:~/tests/httpd-2.0.36$ perl test.pl
doit(): 100
doit_one():10
doit_one():10
ckruse@www:~/tests/httpd-2.0.36$
Gruesse,
CK