8

我已经按照“open”的 linux 手册页中的建议实现了文件锁定机制,其中指出:

想要使用 lockfile 执行原子文件锁定并且需要避免依赖 NFS 对 O_EXCL 支持的便携式程序,可以在同一文件系统上创建唯一文件(例如,合并主机名和 PID),并使用 link(2)创建锁定文件的链接。如果link(2) 返回0,则锁定成功。否则,对唯一文件使用 stat(2) 来检查其链接数是否增加到 2,在这种情况下,锁定也是成功的。

这似乎工作得很好,但是为了在我的测试中获得 100% 的代码覆盖率,我需要涵盖链接数增加到 2 的情况。

我试过谷歌搜索,但我似乎能找到的只是上面提到的与“完成方式”相同的参考。

谁能向我解释一下哪些情况会导致链接失败(返回 -1),但链接数增加到 2?

4

2 回答 2

2

Linux程序员手册的链接(2)页面底部提供了您问题的答案:

   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.
于 2013-12-18T02:05:44.843 回答
1

创建另一个文件比什么都麻烦。而是创建一个目录并检查创建结果。Unix 手册指出,只有一个任务可以成功创建目录,如果目录已经存在,另一个任务将失败,包括两个任务同时尝试的情况。操作系统本身会处理该问题,因此您不必这样做。

如果不是因为可能的陈旧锁,这就是您所要做的。然而,事情发生了,程序中止并且并不总是删除它们的锁。所以实现可以稍微复杂一点。

在脚本中,我经常使用下面的代码。它会自动处理陈旧的锁。您可以在 C 中实现相同的功能。检查手册页:

man -s 2 mkdir

EXECUTION_CONTROL_FILE:是名称 PATH 和 Dir 名称,类似于 /usr/tmp/myAppName

second_of_now:以秒为单位返回当前时间(包括在下面)

LOCK_MAX_TIME:是锁在被认为是陈旧之前可以存在多长时间(以秒为单位)

sleep 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
于 2013-03-14T17:47:38.743 回答