flock, lockf und LockFileEx
« | 11 Sep 2021 | »Der “exklusive” Zugriff auf Dateien spaltet die Nation der Entwickler, wenn es darum geht, wie, wann und wo dieses Kontrollmittel eingesetzt werden soll.
Dateisperren sind eine recht alte Erfindung und deshalb wundert es mich, wie uneinheitlich das Thema behandelt wurde.
Vorgeschichte: PHP
Es fing in einem PHP Kurs vor 20 Jahren (W-T-F wo ist die Zeit hingekommen) an. Ein Gästebuch für die Homepage sollte entstehen, aber ohne Datenbank, nur mit Textdateien. (Ja, so einen Blödsinn haben wir damals alle gemacht und fanden es “cool”.)
Was passiert, wenn zwei Leute gleichzeitig auf “Senden” klicken und zwei
PHP-Prozesse Daten an eine Datei anfügen?
Unter Windows könnte einer einen Zugriffsfehler erhalten, oder beide
schreiben ans gleiche Dateiende und überschreiben sich gegenseitig, was
vor allem unter Linux passieren kann.
Damit das nicht geschieht, sollte man die Datei mit einer “atomaren”
Sperre (also einem “Lock”) belegen, wo ein Zugriff durchkommt und der andere
so lange warten muss, bis der erste fertig ist, damit er ans Ende schreiben
kann. Und die PHP Funktion dafür heißt flock()
.
Das funktionierte damals tatsächlich recht gut, und man findet immer noch Web 1.0 PHP Codes, die genau so ihre Daten vor parallelen Zugriffen schützten: Quasi ein Mutex auf Dateiebene.
Und ich nutze das auf opengate.at
teilweise heute auch noch so.
Win32
Windows hat seit der
DOS Zeit die Möglichkeit bereitgestellt,
Dateien exklusiv öffnen zu lassen. Eigentlich hat Windows eher Probleme mit
“geteilten” Zugriffen und sperrt oft mehr, als man möchte.
Doch nachdem der C Standard
eher dem Unix Modell folgt, findet man
mehrheitlich “gemeinsam” nutzbare Dateien in den Quellen der C-Laufzeit bis
hin zu den Anwendungsprogrammen unter Windows.
Anders gesagt: fopen()
nutzt intern CreateFile()
mit allen möglichen FILE_SHARE_
-Flags.
Wer etwas exklusiv sperren will, tut dies in der Regel mit
LockFileEx()
und kann damit einzelne Bereiche einer Datei gezielt für den exklusiven
Bedarf sperren und danach wieder freigeben.
Ist eine Datei über sein HANDLE
gesperrt, ist es egal, ob ein zweiter
Prozess oder ein eigener zweiter Thread darauf zugreifen möchte, denn beide
werden fehlschlagen bzw. blockieren, bis die Sperre per
UnlockFile
freigegeben wurde.
Um eine ganze Datei zu sperren kann man Offset 0
und für die zu sperrende
Länge ruhig 0xffffffffffffffff
angeben.
Die PHP Implementierung von flock
benutzt unter Windows auch genau diesen
Trick, wie man in flock_compat.c
im PHP Quellcode schön sehen kann.
POSIX, BSD und Linux
Wie “cool” wäre es, wenn flock
eine POSIX
API wäre und damit auf allen Unixvarianten verfügbar wäre. Doch leider ist das
nicht so und außerdem muss man hier genau wissen, was man eigentlich will.
Denn während Windows Dateien wirklich sperrt (ReadFile()
und WriteFile()
schlagen fehl), sind Locks in Linux so genannte “advistory locks” und
funktionieren sperrend nur beim Aufruf Sperrfunktionen selbst und nicht
bei read()
und write()
.
Alle Prozesse müssen also einen Lock anfordern um zu warten bis ein anderer
eine Sperre aufgibt.
Jetzt gibt es zwei APIs dafür:
flock
ist eine BSD API, die aber auch in Linux und Android normalerweise enthalten ist und wirkt auf einen geöffneten Dateideskriptor.flock
sperrt dabei die gesamte Datei und egal ob zwei Prozesse oder zwei Threads die gleiche Datei öffnen und sperren, nur ein Thread in einem Prozess kann einen exklusiven Lock zu einem Zeitpunkt halten.lockf
ist einePOSIX
API und stellt Sperren für Bereiche innerhalb einer Datei bereit (also wieLockFileEx
). Die Sperren wirken aber nur auf Prozess-Ebene, wenn also zwei Threads einen exklusiven Lock auf die gleiche Datei anfordern, bekommen beide einen und dann sind Korruptionen meist die Folge.
“Mandatory locks” wie unter Windows sind in POSIX
nicht spezifiziert, wenn
gleich man unter Linux (ohne Garantien) auch an so etwas herankommen kann.
Standard ist das dann aber keiner.
GATE Implementierung
Tja, der kleinste gemeinsame Nenner ist für mich ein Lock auf eine gesamte Datei, der sowohl Thread-sicher als auch Prozess-sicher sein soll.
Und das erreiche ich unter Windows mit LockFileEx
und unter BSD und Linux
mit flock
. Sollte aber flock
nicht unterstüzt werden, hätte ich im
Platform-Support-Layer auch einen Fallback parat, der lockf
nutzt um
flock
zumindest auf Prozessebene zu simulieren.
Aktiv eingesetzt wird das Feature bei Logdateien. Denn so können mehrere Threads und Prozesse in eine Datei schreiben ohne sich gegenseitig zu stören.
Fazit
Dateisperren kommen häufiger zum Einsatz, als man glaubt. CONAN setzt beispielsweise Lock-Dateien für die Integrität seines Caches ein, damit nicht zwei CONAN Prozesse das gleiche Projekt parallel neu bauen können.
Das Schöne an Lockdateien ist, dass sie wesentlich schneller sein können, als wenn man alles über monolithische Zusatzdienste leitet, die dann system-weite Mutexe einsetzen.
Und dennoch bin ich enttäuscht, dass flock
nicht Teil von POSIX
ist
und generell wieder bei jedem Unix-Derivat die Raterei anfängt, welche
der Funktionen “aus Gottes Gnaden” vorhanden ist.
Android kann z.B. nur flock
und kein lockf
… es ist echt komisch.
However … Dateisperren: Check.