2013-02-27 19 views
8

ich eine Datei implementiert haben nach dem Vorbild der Vorschlag aus dem Linux-man-Seite für „öffnen“, in dem es heißt Verriegelungsmechanismus:Implementierung eines tragbaren Datei Verriegelungsmechanismus

Portable Programme, die Atom-Dateisperrung durchführen möchten Verwenden Sie eine Lockfile, und müssen die Abhängigkeit von NFS-Unterstützung für O_EXCL vermeiden, können eine eindeutige Datei auf dem gleichen Dateisystem erstellen (z. B. Hostname und PID), und verwenden Sie Link (2), um eine Verknüpfung zu der Sperrdatei . Wenn link (2) 0 zurückgibt, ist die Sperre erfolgreich. Andernfalls verwenden Sie stat (2) unter die eindeutige Datei, um zu überprüfen, ob die Anzahl der Links auf 2 gestiegen ist, in . In diesem Fall ist die Sperre ebenfalls erfolgreich.

Dies scheint perfekt zu arbeiten, aber 100% Code Coverage in meinen Tests zu bekommen, muss ich den Fall abzudecken, in denen die Verbindung Zahl auf 2 erhöht wird googeln

Ich habe versucht, aber alle Ich scheine in der Lage zu sein, zu finden, ist der gleiche Verweis oben erbrochen wie "wie es gemacht wird".

Kann mir jemand erklären, welche Umstände die Verbindung zum Scheitern bringen würden (gibt -1 zurück), aber die Anzahl der Links wird auf 2 erhöht?

+0

Sehr gute Frage . Ich kann mir keine Umstände vorstellen, unter denen dies passieren würde, es sei denn, zwei konkurrierende Prozesse wählten gleichzeitig denselben eindeutigen Dateinamen (was natürlich schlecht wäre). Könnte ein Workaround für sehr alte NFS-Fehler sein? – Celada

+1

Müssen Sie Lockfiles über NFS erstellen?AFAIK sollten Sie 'flock()' oder 'lockf()' in den meisten Fällen verwenden können. – Hasturkun

Antwort

2

Die Antwort auf Ihre Frage am Ende des Lenkers vorgesehen ist (2) Seite des Handbuchs des Linux-Programmierer:

On NFS file systems, the return code may be wrong in case the NFS 
    server performs the link creation and dies before it can say so. Use 
    stat(2) to find out if the link got created. 
1

Erstellen einer anderen Datei ist mehr Ärger als alles andere. Erstellen Sie stattdessen ein Verzeichnis und überprüfen Sie das Ergebnis der Erstellung. Das Unix-Handbuch besagt, dass nur ein Task erfolgreich ein Verzeichnis erstellen kann, der andere erhält einen Fehler, wenn das Verzeichnis bereits existiert, einschließlich des Falls, in dem 2 Tasks gleichzeitig versucht haben. Das Betriebssystem selbst behandelt das Problem, sodass Sie es nicht müssen.

Wenn es nicht für mögliche veraltete Schlösser war, ist das alles, was Sie tun müssten. Es passieren jedoch Dinge, Programme brechen ab und entfernen nicht immer ihre Sperre. Die Implementierung kann also etwas aufwendiger sein.

In einem Skript habe ich oft den folgenden Code verwendet. Es verarbeitet veraltete Sperren automatisch.

man -s 2 mkdir 

EXECUTION_CONTROL_FILE: Sie können das gleiche in C. Überprüfen Manpage implementieren ist ein Name PATH und Dir Namen, so etwas wie/usr/tmp/myAppName

second_of_now: Rückkehr die aktuelle Zeit in Sekunden (auch weiter unten)

LOCK_MAX_TIME: ist, wie lange in Sekunden ein Schloss kann es vorhanden ist, bevor 5

Schlaf veraltet angesehen wird: es wird immer davon ausgegangen, dass eine Sperre etwas kurz und süß tun wird. Wenn nicht, sollte Ihr Schlafzyklus länger sein.

LockFile() { 
    L_DIR=${EXECUTION_CONTROL_FILE}.lock 
    L_DIR2=${EXECUTION_CONTROL_FILE}.lock2 
    (
    L_STATUS=1 
    L_FILE_COUNT=2 
    L_COUNT=10 
    while [ $L_STATUS != 0 ]; do 
    mkdir $L_DIR 2>/dev/null 
    L_STATUS=$? 
    if [ $L_STATUS = 0 ]; then 
     # Create the timetime stamp file 
     second_of_now >$L_DIR/timestamp 
    else 
     # The directory exists, check how long it has been there 
     L_NOW=`second_of_now` 
     L_THEN=`cat $L_DIR/timestamp 2>/dev/null` 
     # The file does not exist, how many times did this happen? 
     if [ "$L_THEN" = "" ]; then 
     if [ $L_FILE_COUNT != 0 ]; then 
      L_THEN=$L_NOW 
      L_FILE_COUNT=`expr $L_FILE_COUNT - 1` 
     else 
      L_THEN=0 
     fi 
     fi 
     if [ `expr $L_NOW - $L_THEN` -gt $LOCK_MAX_TIME ]; then 
     # We will try 10 times to unlock, but the 10th time 
     # we will force the unlock. 
     UnlockFile $L_COUNT 
     L_COUNT=`expr $L_COUNT - 1` 
     else 
     L_COUNT=10 # Reset this back in case it has gone down 
     sleep 5 
     fi 
    fi 
    done 
) 
    L_STATUS=$? 
    return $L_STATUS 
} 

#### 
#### Remove access lock 
#### 
UnlockFile() { 
    U_DIR=${EXECUTION_CONTROL_FILE}.lock 
    U_DIR2=${EXECUTION_CONTROL_FILE}.lock2 
    (
    # This 'cd' fixes an issue with UNIX which sometimes report this error: 
    # rm: cannot determine if this is an ancestor of the current working directory 
    cd `dirname "${EXECUTION_CONTROL_FILE}"` 

    mkdir $U_DIR2 2>/dev/null 
    U_STATUS=$? 
    if [ $U_STATUS != 0 ]; then 
    if [ "$1" != "0" ]; then 
     return 
    fi 
    fi 

    trap "rm -rf $U_DIR2" 0 

    # The directory exists, check how long it has been there 
    # in case it has just been added again 
    U_NOW=`second_of_now` 
    U_THEN=`cat $U_DIR/timestamp 2>/dev/null` 
    # The file does not exist then we assume it is obsolete 
    if [ "$U_THEN" = "" ]; then 
    U_THEN=0 
    fi 
    if [ `expr $U_NOW - $U_THEN` -gt $LOCK_MAX_TIME -o "$1" = "mine" ]; then 
    # Remove lock directory as it is still too old 
    rm -rf $U_DIR 
    fi 

    # Remove this short lock directory 
    rm -rf $U_DIR2 
) 
    U_STATUS=$? 
    return $U_STATUS 
} 

#### 
second_of_now() { 
    second_of_day `date "+%y%m%d%H%M%S"` 
} 

#### 
#### Return which second of the date/time this is. The parameters must 
#### be in the form "yymmddHHMMSS", no centuries for the year and 
#### years before 2000 are not supported. 
second_of_day() { 
    year=`printf "$1\n"|cut -c1-2` 
    year=`expr $year + 0` 
    month=`printf "$1\n"|cut -c3-4` 
    day=`printf "$1\n"|cut -c5-6` 
    day=`expr $day - 1` 
    hour=`printf "$1\n"|cut -c7-8` 
    min=`printf "$1\n"|cut -c9-10` 
    sec=`printf "$1\n"|cut -c11-12` 
    sec=`expr $min \* 60 + $sec` 
    sec=`expr $hour \* 3600 + $sec` 
    sec=`expr $day \* 86400 + $sec` 
    if [ `expr 20$year % 4` = 0 ]; then 
    bisex=29 
    else 
    bisex=28 
    fi 
    mm=1 
    while [ $mm -lt $month ]; do 
    case $mm in 
     4|6|9|11) days=30 ;; 
     2) days=$bisex ;; 
     *) days=31 ;; 
    esac 
    sec=`expr $days \* 86400 + $sec` 
    mm=`expr $mm + 1` 
    done 
    year=`expr $year + 2000` 
    while [ $year -gt 2000 ]; do 
    year=`expr $year - 1` 
    if [ `expr $year % 4` = 0 ]; then 
     sec=`expr 31622400 + $sec` 
    else 
     sec=`expr 31536000 + $sec` 
    fi 
    done 
    printf "$sec\n" 
} 

Verwendung wie folgt aus:

# Make sure that 2 operations don't happen at the same time 
    LockFile 
    # Make sure we get rid of our lock if we exit unexpectedly 
    trap "UnlockFile mine" 0 
. 
. Do what you have to do 
. 
    # We need to remove the lock 
    UnlockFile mine 
+0

Das sieht rassig aus, IMHO. Welche Atomizität Sie auch von Ihrem 'mkdir()' (das Sie auch durch das Erstellen einer Datei mit 'O_EXCL' erhalten können) erhalten haben, verlieren Sie später beim Zugriff auf die Timestamp-Datei. Wenn Sie sich in einem Shell-Skript befinden, verwenden Sie wahrscheinlich den Befehl 'flock' von util-linux. – Hasturkun

+0

Die Absicht hier war, eine Technik zu beschreiben, die ausschließlich Skriptsprache verwendet und immer noch mit C-Code kompatibel wäre, der auf dieselbe Ressource zugreifen möchte. Die Verwendung von "mkdir" und die Überprüfung des Rückgabecodes wären ausreichend gewesen, aber das würde die veraltete Sperre nicht behandeln. Wenn Ihr Sperrmechanismus ausschließlich von C-Code verwendet wird, dann haben Sie viel bessere Funktionen zur Verfügung und ich stimme zu, das wäre mehr ein Schmerz. Ich frage mich, warum es mit allen in Unix verfügbaren Werkzeugen kein Werkzeug gab, das geschrieben wurde (oder das sich verfing), um Sperren in Skripten zu manipulieren. – cpu

+0

Es gibt ein Werkzeug zum Manipulieren von Sperren in Skripts, wie ich bereits erwähnt habe, das Dienstprogramm [flock] (http://man7.org/linux/man-pages/man1/flock.1.html) aus dem [util-linux ] (https://www.kernel.org/pub/linux/utils/util-linux/) Paket macht das. siehe auch http://stackoverflow.com/a/1985512 – Hasturkun