Kleiner Webcounter - Problem mit Schreiben/Lesen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
ne0h
User
Beiträge: 115
Registriert: Samstag 16. Februar 2008, 11:35

Samstag 31. Mai 2008, 22:28

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

Samstag 31. Mai 2008, 22:48

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.
ne0h
User
Beiträge: 115
Registriert: Samstag 16. Februar 2008, 11:35

Samstag 31. Mai 2008, 23:11

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
lunar

Samstag 31. Mai 2008, 23:38

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.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Sonntag 1. Juni 2008, 06:44

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.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Sonntag 1. Juni 2008, 08:30

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
lunar

Sonntag 1. Juni 2008, 09:45

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Sonntag 1. Juni 2008, 10:05

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
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Sonntag 1. Juni 2008, 12:38

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.
TUFKAB – the user formerly known as blackbird
ne0h
User
Beiträge: 115
Registriert: Samstag 16. Februar 2008, 11:35

Sonntag 1. Juni 2008, 13:15

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
lunar

Sonntag 1. Juni 2008, 13:53

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 ;)
Zuletzt geändert von lunar am Sonntag 1. Juni 2008, 15:25, insgesamt 1-mal geändert.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Sonntag 1. Juni 2008, 15:14

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
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
lunar

Sonntag 1. Juni 2008, 15:26

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 ;)
ne0h
User
Beiträge: 115
Registriert: Samstag 16. Februar 2008, 11:35

Sonntag 1. Juni 2008, 18:51

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
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Sonntag 1. Juni 2008, 20:46

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:

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten