2013-02-27 2 views
8

я реализовал механизм блокировки файлы вдоль линий по предложению со страницы линукса людей для «открытия», в котором говорится:Реализация портативного механизма блокировки файлов

Портативных программы, которые хотят выполнить атомную блокировку файла используя файл блокировки , и необходимо избегать поддержки NFS для O_EXCL, может создать уникальный файл в той же файловой системе (например, включить имя хоста и PID) и использовать ссылку (2), чтобы сделать ссылку на файл блокировки , Если ссылка (2) возвращает 0, блокировка будет успешной. В противном случае используйте stat (2) на уникальный файл, чтобы проверить, увеличилось ли его количество ссылок до 2, в , в случае которого блокировка также успешна.

Это, кажется, работает прекрасно, однако, чтобы получить покрытие кода 100% в моем тестировании, мне нужно, чтобы покрыть случай, когда счетчик ссылок увеличивается до 2.

Я пробовал прибегая к помощи, но все Кажется, я могу найти ту же ссылку, что и выше, как «способ, которым это делается».

Может кто-нибудь объяснить мне, какой набор обстоятельств приведет к сбою ссылки (возвращает -1), но количество ссылок увеличивается до 2?

+0

Очень хороший вопрос , Я не могу придумать никаких обстоятельств, при которых это произойдет, если только два конкурирующих процесса одновременно не выбрали одно и то же уникальное имя файла (что, очевидно, было бы плохим). Может быть обходным путем для очень старых ошибок NFS? – Celada

+1

У вас есть необходимость создавать lockfiles над NFS?AFAIK вы должны иметь возможность использовать 'flock()' или 'lockf()' в большинстве случаев. – Hasturkun

ответ

2

Ответ на ваш вопрос предусмотрен в нижней части ссылки (2) страницы справочного руководства по Linux программиста:

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

Создание другого файла - это больше проблем, чем что-либо. Создайте каталог и проверьте результат создания. В руководстве Unix указано, что только одна задача может быть успешной при создании каталога, а другая - сбое, если каталог уже существует, в том числе случай, когда 2 задачи пытались одновременно. Сама ОС обрабатывает проблему, поэтому вам не нужно.

Если бы не пустые замки, это все, что вам нужно было бы сделать. Однако все происходит, программы прерываются и не всегда удаляют блокировку. Таким образом, реализация может быть немного более сложной.

В сценарии я часто использовал код ниже. Он автоматически обрабатывает устаревшие блокировки. Вы можете осуществить то же самое в C. Проверьте страницы человека:

man -s 2 mkdir 

EXECUTION_CONTROL_FILE: это путь и имя Dir, что-то вроде/USR/TMP/myAppName

second_of_now: возвращает текущее время в секундах (приводится ниже)

LOCK_MAX_TIME: сколько времени в секундах существует блокировка может, прежде чем он считается устаревшим

сна 5: всегда предполагается, что блокировка будет делать что-то коротким и сладким. Если нет, возможно, ваш цикл сна должен быть длиннее.

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

Использование так:

# 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

Это выглядит потрясающе, ИМХО. Независимо от атомарности, которую вы, возможно, получили из вашего 'mkdir()' (который, кстати, вы также можете получить от создания файла с 'O_EXCL'), вы теряете его позже при доступе к файлу метки времени. Если вы находитесь в сценарии оболочки, вам, вероятно, лучше использовать команду 'flock' из util-linux. – Hasturkun

+0

Целью здесь было описать технику, которая использует исключительно язык сценариев и все еще может быть совместима с кодом C, который хотел бы получить доступ к одному и тому же ресурсу. Использовать «mkdir» и проверять код возврата было бы достаточно, но это не будет обрабатывать устаревшую блокировку. Если ваш механизм блокировки используется исключительно кодом C, то у вас есть в вашем распоряжении гораздо лучшие функции, и я согласен, это было бы больнее. Интересно, почему, со всеми инструментами, доступными в Unix, не было инструмента, написанного (или пойманного) для манипулирования блокировками в скриптах. – cpu

+0

Инструмент существует для управления блокировками в сценариях, как я уже упоминал ранее, утилитой [flock] (http://man7.org/linux/man-pages/man1/flock.1.html) из [util-linux ] (https://www.kernel.org/pub/linux/utils/util-linux/) пакет делает это. см. также http://stackoverflow.com/a/1985512 – Hasturkun

 Смежные вопросы

  • Нет связанных вопросов^_^