2013-02-27 13 views
8

blokowania I wprowadziły plik mechanizmu wzdłuż linii sugestią ze strony linux man dla „open” zamek, który stwierdza:Wdrażanie przenośny plik mechanizm

przenośne programy, które chcą wykonać atomową blokowanie plików przy użyciu pliku blokującego i nie należy polegać na obsłudze NFS dla O_EXCL, można utworzyć unikatowy plik w tym samym systemie plików (np. z włączoną nazwą hosta i PID ) i użyć łącza (2), aby utworzyć łącze do pliku blokującego . Jeśli link (2) zwróci 0, blokada się powiedzie. W przeciwnym razie użyj stat (2) na unikalnym pliku, aby sprawdzić, czy liczba jego linków wzrosła do 2, w , w którym przypadku blokada również zakończyła się powodzeniem.

To wydaje się doskonale działa, jednak aby uzyskać 100% pokrycia kodu w moich testów, muszę pokryć przypadek, w którym liczba łącze jest zwiększony do 2.

Próbowałem googling, ale wszystko Wydaje mi się, że jestem w stanie znaleźć to to samo odniesienie powyżej wydalone, jak "sposób, w jaki to zrobiono".

Czy ktoś może mi wyjaśnić, jaki zestaw okoliczności spowodowałby niepowodzenie łącza (zwraca -1), ale liczba linków wzrosła do 2?

+0

bardzo dobre pytanie . Nie mogę wymyślić żadnych okoliczności, w których to by się stało, chyba że dwa konkurujące procesy wybrałyby tę samą unikalną nazwę pliku w tym samym czasie (co oczywiście byłoby złe). Czy możliwe jest obejście bardzo starych błędów NFS? – Celada

+1

Czy chcesz utworzyć pliki blokujące przez NFS?AFAIK powinieneś móc w większości przypadków używać 'flock()' lub 'lockf()'. – Hasturkun

Odpowiedz

2

Odpowiedź na to pytanie znajduje się na dole link (2) stronie Instrukcji programisty Linuksa:

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

Tworzenie innego pliku to więcej problemów niż cokolwiek innego. Utwórz zamiast tego katalog i sprawdź wynik utworzenia. Podręcznik Unixa stwierdza, że ​​tylko jedno zadanie może odnieść sukces w tworzeniu katalogu, a druga otrzyma awarię, jeśli katalog już istnieje, w tym przypadek, w którym 2 zadanie wypróbowało go w tym samym czasie. Sam system operacyjny obsługuje ten problem, więc nie musisz.

Jeśli nie było możliwe nieaktualne zamki, to wszystko, co musiałbyś zrobić. Jednak zdarza się, że programy się kończą i nie zawsze usuwają ich blokadę. Tak więc implementacja może być nieco bardziej rozbudowana.

W skrypcie często używam poniższego kodu. Obsługuje nieaktualne blokady automatycznie. Można zaimplementować samo w C. stronie Sprawdź Man:

man -s 2 mkdir 

EXECUTION_CONTROL_FILE: to nazwa ścieżki i nazwa Dir, coś jak/usr/tmp/myAppName

second_of_now: powrót aktualny czas (w sekundach zawarte poniżej)

LOCK_MAX_TIME: jak długo w sekundach może zablokować istnieje przed uważa się zestarzeć

sleep 5: to jest zawsze zakłada się, że blokada będzie zrobić coś krótkie i słodkie. Jeśli nie, być może twój cykl snu powinien być dłuższy.

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" 
} 

Zastosowanie tak:

# 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

To wygląda na bardziej wytworne, IMHO. Bez względu na to, jaką atomowość dostałeś od swojego 'mkdir()' (które, btw, możesz także uzyskać z pliku o 'O_EXCL'), tracisz później podczas uzyskiwania dostępu do pliku sygnatury czasowej. Jeśli jesteś w skrypcie powłoki, prawdopodobnie lepiej jest użyć polecenia 'flock' z util-linux. – Hasturkun

+0

Chodziło o opisanie techniki, która używa wyłącznie języka skryptowego i nadal mogłaby być zgodna z kodem C, który chciałby uzyskać dostęp do tego samego zasobu. Używanie "mkdir" i sprawdzanie kodu powrotu byłoby wystarczające, ale to nie poradziłoby ze stałą blokadą. Jeśli twój mechanizm blokujący jest używany wyłącznie przez kod C, masz do dyspozycji znacznie lepsze funkcje i zgadzam się, byłby to większy problem. Zastanawiam się, dlaczego, przy użyciu wszystkich narzędzi dostępnych w systemie Unix, nie było napisane (lub przechwycone) narzędzie do manipulowania blokadami w skryptach. – cpu

+0

Istnieje narzędzie do manipulowania blokadami w skryptach, jak już wspomniałem, narzędzie [flock] (http://man7.org/linux/man-pages/man1/flock.1.html) z [util-linux ] (https://www.kernel.org/pub/linux/utils/util-linux/) pakiet to robi. patrz także http://stackoverflow.com/a/1985512 – Hasturkun