Seite 1 von 2

Kleiner Webcounter - Problem mit Schreiben/Lesen

Verfasst: Samstag 31. Mai 2008, 22:28
von ne0h
Hi,

ich habe mir für meine Webseite ein kleines Pythonscript geschrieben, dass als CGI Script fungiert und einen kleinen Besucherzähler darstellt.

Ich würde gerne wissen, ob Ihr es anders gemacht hättet und dazu noch meine Frage: Warum kann ich nur mit den Modi "a" und "w" arbeiten und nicht die weiteren Modi wie "a+" oder "w+" usw.? Immer wenn ich damit arbeite, dann kann ich entweder nicht gleichzeitig lesen oder schreiben, obwohl das doch dafür gedacht ist? Ich erhalte immer einen IO-Error.

Hier das Script:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding:utf-8 -*-

print "Content-Type: text/plain;charset=utf-8;\n"

try:
	fileobj = open("count.txt", "r")
	count = fileobj.read()
	fileobj.close()
	if count == "":
		count = "1"
		print count
		fileobj = open("count.txt", "w")
		fileobj.write(count)
		fileobj.close()
	else:
		count = int(count)
		count += 1
		print count
		count = str(count)
		fileobj = open("count.txt", "w")
		fileobj.write(count)
		fileobj.close()
except:
	fileobj = None
	print "FEHLER: Counter konnte nicht gelesen werden!"

Gruss

ne0h

P.S.: Weil die erweiterten Modi nicht funktionieren, öffne ich die Datei auch so oft....

Verfasst: Samstag 31. Mai 2008, 22:48
von BlackJack
Iiihk ein ``except`` ohne konkrete Ausnahme.

Du suchst vielleicht 'r+', aber auch das hilft nicht wirklich weil Dein Programm nicht zuverlässig funktioniert. Wenn mehr als ein Zugriff auf den Zähler zur "gleichen" Zeit erfolgen kann, muss man auf all die hässlichen Sachen achten, die bei nebenläufiger Programmierung so passieren können.

Verfasst: Samstag 31. Mai 2008, 23:11
von ne0h
Hi,

Ok, die Exception könnte besser sein, das sehe ich ein. Es dient mir auch erstmal nur grob zum testen der Funktionalität.

Aber was meinst Du genau mit:
Wenn mehr als ein Zugriff auf den Zähler zur "gleichen" Zeit erfolgen kann, muss man auf all die hässlichen Sachen achten, die bei nebenläufiger Programmierung so passieren können.
Ich verstehe worauf Du hinaus willst, aber was soll ich den "Nebenläufigkeiten" entnehmen bzw. wie soll das aussehen?

gruß

ne0h

Verfasst: Samstag 31. Mai 2008, 23:38
von lunar
Wenn zwei Requests zur gleichen Zeit ankommen, passiert folgendes:

Der Server startet zwei CGI-Prozesse gleichzeitig. Der erste öffnet jetzt die Datei, schreibt seinen Wert, und wird von Scheduler schlafen gelegt. Jetzt kommt der zweite CGI-Prozess dran, öffnet die Datei, sieht noch den _alten_ Wert, erhöht diesen um eins, und schreibt die Datei. Jetzt kommt wieder der erste Prozess an die Reihe, und schreibt seinen Wert: voilá, schon ist ein Besuch im Datennirvana verschwunden.

Das ist noch eines der harmloseren Dinge, ein sich verringernder Zähler ist auch gut vorstellbar, wenn ein Prozess schlafen gelegt wird, zwei andere den Counter herhöhen, und dann der erste Prozess seine veralteten Daten ausschreibt.

Her wirst du dem entweder, in dem du eine Datenbank einsetzt, oder die Datei lockst, während du sie öffnest.

Eine Datenbank oder zumindest die Benutzung von Shared Memory wären eh anzuraten, da dauernder Datei-IO nicht gerade performant ist. Auch ist Locking bei Shared Memory einfacher als bei Dateien.

Bei einer Datenbank muss man sich dann gar nicht mehr darum kümmern, da Transaktionen in der Regel atomar ablaufen.

Re: Kleiner Webcounter - Problem mit Schreiben/Lesen

Verfasst: Sonntag 1. Juni 2008, 06:44
von Leonidas
ne0h hat geschrieben:Ich würde gerne wissen, ob Ihr es anders gemacht hättet
Ja ganz sicher sogar. Worum sollte ich so einen Quatsch überhaupt machen, wenn mein Webserver schon seine ``access.log`` schreibt?

Merke: eine pythonische Lösung muss nicht immer durch ein Python-Programm realisiert werden.

Verfasst: Sonntag 1. Juni 2008, 08:30
von sma
Andere haben ja schon darauf hingewiesen, dass die gezeigte Lösung nicht thread-safe ist und eine "racing condition" enthält. Es darf immer nur ein Prozess auf die Datei "count.txt" zugreifen. Der klassische Weg unter Unix (ohne File Locking), dies zu gewährleisten wäre, eine zweite Datei als Semaphore zu benutzen. Alle Unix-Syscalls sind atomar und mit `creat("...", O_CREAT+O_EXCL)` kann man daher eine Datei erzeugen, dann und nur dann wenn sie noch nicht existiert. Andernfalls gibt es ein Fehler. In Python kann man `os.open("...", os.O_CREAT+os.O_EXCL)` benutzen.

In einer Schleife muss jeder Prozess versuchen, die "Lock-Datei" anzulegen und erst wenn das gelingt, darf ein Zugriff auf "count.txt" erfolgen. Danach ist die erste Datei wieder zu löschen.

Alternativ kann man auch `O_EXLOCK` oder `fcntl.flock()` benutzen, das ist jedoch weniger portabel und letzteres z.B. nicht unter Windows verfügbar, sagt die Doku.

Stefan

Verfasst: Sonntag 1. Juni 2008, 09:45
von lunar
sma hat geschrieben:Alle Unix-Syscalls sind atomar
Im Linux-Kernel sind auch Syscalls dem Scheduling unterworfen:

CONFIG_PREEMPT_VOLUNTARY:
[...]This allows reaction to interactive events by allowing a
low priority process to voluntarily preempt itself even if it
is in kernel mode executing a system call[...]

CONFIG_PREEMPT:
[...]This allows reaction to interactive events by
permitting a low priority process to be preempted involuntarily
even if it is in kernel mode executing a system call and would
otherwise not be about to reach a natural preemption point.[...]

Zumindest die erste Option dürfte bei den meisten Desktop-Distributionen gesetzt sein.

Verfasst: Sonntag 1. Juni 2008, 10:05
von sma
Okay, meine Information aus der Betriebssystem-Vorlesung vor vielen, vielen Jahren ist veraltet und/oder fehlerhaft erinnert. Allerdings garantiert auch Linux, dass `open()` mit besagten Parametern atomar ist. Meine Verallgemeinerung war nur falsch.

Stefan

Verfasst: Sonntag 1. Juni 2008, 12:38
von mitsuhiko
lunar hat geschrieben:Der Server startet zwei CGI-Prozesse gleichzeitig. Der erste öffnet jetzt die Datei, schreibt seinen Wert, und wird von Scheduler schlafen gelegt. Jetzt kommt der zweite CGI-Prozess dran, öffnet die Datei, sieht noch den _alten_ Wert, erhöht diesen um eins, und schreibt die Datei. Jetzt kommt wieder der erste Prozess an die Reihe, und schreibt seinen Wert: voilá, schon ist ein Besuch im Datennirvana verschwunden.
Wenn ich nicht falsch lieg können auch alle verschwinden. Ich glaub nach file(x, 'w') wird geflusht. Wenn in dem moment der Prozess kommt und eine Leere Datei sieht setzt er den Counter auf 1 zurück.

Verfasst: Sonntag 1. Juni 2008, 13:15
von ne0h
Ja ganz sicher sogar. Worum sollte ich so einen Quatsch überhaupt machen, wenn mein Webserver schon seine ``access.log`` schreibt?

Weil ich auf meinem Schulserver arbeite und dort nur sehr beschränkte Zugriffsrechte habe? :?


Die Exception arbeite ich noch um, die restlichen Vorschläge muss ich mir erstmal in Ruhe zu Gemüte führen, ich verstehe nicht wirklich alles, was hier geschrieben wurde, z.B. das hier:
Der klassische Weg unter Unix (ohne File Locking), dies zu gewährleisten wäre, eine zweite Datei als Semaphore zu benutzen. Alle Unix-Syscalls sind atomar und mit `creat("...", O_CREAT+O_EXCL)` kann man daher eine Datei erzeugen, dann und nur dann wenn sie noch nicht existiert. Andernfalls gibt es ein Fehler. In Python kann man `os.open("...", os.O_CREAT+os.O_EXCL)` benutzen.

Aber Danke schonmal, ich werde mich mal an die Dokus setzen und das nachlesen...


ne0h

Verfasst: Sonntag 1. Juni 2008, 13:53
von lunar
Ich persönlich würde versuchen, ein Lockfile zu vermeiden. Immerhin verursacht das jedes Mal vier IO-Operationen (Lockfile erstellen, Counter lesen, Counter schreiben, Lockfile löschen). Selbst wenn es wie "Overkill" aussieht, würde ich SQLite nehmen. Das nutzt effizienteres Locking, außerdem musst du dich dann überhaupt nicht mehr selbst darum kümmern.

Edit: Ich würde Lockdateien meiden, und nicht verwenden ;)

Verfasst: Sonntag 1. Juni 2008, 15:14
von gerold
lunar hat geschrieben:Ich persönlich würde versuchen, ein Lockfile zu verwenden.
Hallo!

Vor ein paar Jahren, habe ich für ein Onlinespiel eine CGI-Bestenliste programmiert. Vielleicht würde ich heute einiges anders machen, aber ich habe jetzt keine Lust, den Code durchzugehen.

Vielleicht als Lockfile-Beispiel zu gebrauchen:
http://paste.pocoo.org/show/58956/

Was ich noch weiß: Ich verwende im Code Lockfiles um ein gleichzeitiges Beschreiben der Textdatei zu verhindern.

mfg
Gerold
:-)

Verfasst: Sonntag 1. Juni 2008, 15:26
von lunar
Ich habe mal wieder nicht auf den Vorschau-Button geklickt, deswegen ist mir im Posting ein Fehler unterlaufen: Logdateien würde ich so weit als möglich vermeiden wollen, weil es zum einen relativ aufwändig zu programmieren ist, und zum anderen nicht sehr effizient.

Bibliotheken wie SQlite, die diesen Job für mich machen, finde ich da deutlich besser ;)

Verfasst: Sonntag 1. Juni 2008, 18:51
von ne0h
Hallo,

vielen Dank nochmal für die ganzen Tips. Ich habe momentan erst nur eine kleine Testseite, an der ich herumwerkel und an welcher ich vor allem erstmal lerne, Pythonscripte mit CGI zu benutzen.

Eine Datenbank wird später noch folgen, und meine Möglichkeiten auf dem Server sind auch nicht so gewaltig, deshalb fange ich erstmal klein an. :wink:


@gerold


Deinen verlinkten Code habe ich mir angesehen und dort wurde mir gleich die frage beantwortet, wie ich auf die Elemente eines Formulars zugreife.

Code: Alles auswählen

fs = cgi.FieldStorage()

stunden = fs.getvalue("stunden")

In meinem Test-Script klappt dieser Aufruf nun auch endlich (ich habe davor gesucht und in meinen Büchern gewühlt, aber nix gefunden, ausser einem Beispiel mit dem pickle Modul, mit dem ich aber nicht viel anfangen konnte).

Der Ablauf mit "lockfile" ist mir einigermaßen klar, aber direkt umsetzen kann ich es noch nicht so wirklich. Ich werde mal damit rumexperimentieren und Deinen Code als Hilfe einsetzen (aus praxisnahen Beispielen kann man eben immer noch mehr lernen, als aus reiner Theorie :wink: )


Gruß

ne0h

Verfasst: Sonntag 1. Juni 2008, 20:46
von jens
Offtopic: Prima das dieses Thema gerade jetzt hier auftauchte. Einen einfachen Counter ist eine prima Möglichkeit die neuen PyLucid plugin models zu nutzten und zu testen.
Mit http://pylucid.net:8080/pylucid/changeset/1613 gibt es in PyLucid also einen neuen einfachen Page Counter samt unittest :lol:

Verfasst: Sonntag 1. Juni 2008, 20:58
von ne0h
Hallo Jens,

ich würde das ja nur zu gerne tun, aber ich habe keinen Zugriff auf den Server bzw. auf Python selbst. Ich kann Python nur ansprechen, auf die Installation selbst (Ich muss momentan mit Python 2.2.1 vorlieb nehmen) und auf etwaige Frameworks habe ich keinen Einfluss.

gruß

ne0h

Verfasst: Sonntag 1. Juni 2008, 21:39
von audax
python 2.2.1?
Das hat schon lange keinen Support mehr...
Ich würd mir an deiner Stelle nen anderen Server suchen...nachher bist du noch an irgendwelchen Sicherheitsproblemen schuld...

Verfasst: Sonntag 1. Juni 2008, 22:04
von ne0h
Das ist mein Schulserver. :wink:

Ich habe keine Lust mir eigenen Space auf einem Server zu kaufen, ich will eigentlich erstmal nur rumspielen und testen und dort habe ich halt alles umsonst....


ne0h

Verfasst: Sonntag 1. Juni 2008, 23:03
von audax
hol dir lieber für 3,50€ nen vServer oder was vergleichbares. Python2.2 ist nun wirklich nicht schön.

Verfasst: Sonntag 1. Juni 2008, 23:05
von lunar
@audax
Nicht, dass du nicht recht hättest, nur ist das gerade wenig zielführend ;)