flock, lockf und LockFileEx

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 eine POSIX API und stellt Sperren für Bereiche innerhalb einer Datei bereit (also wie LockFileEx). 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.