实现可移植文件锁定机制

时间:2013-02-27 15:59:59

标签: linux lockfile

我已经实现了一个文件锁定机制,沿着linux手册页中“open”的建议,其中说明:

  

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

这看起来效果很好,但是为了在测试中获得100%的代码覆盖率,我需要覆盖链接数增加到2的情况。

我已经尝试使用谷歌搜索了,但我似乎能找到的所有内容都是上面反复出现的“完成它的方式”。

有人可以向我解释一下哪种情况会导致链接失败(返回-1),但链接数会增加到2?

2 个答案:

答案 0 :(得分: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.

答案 1 :(得分:1)

创建另一个文件比任何事情都麻烦。改为创建一个目录并检查创建的结果。 Unix手册指出只有一个任务可以成功创建一个目录,另一个任务会在目录已经存在时失败,包括2个任务同时尝试它的情况。操作系统本身可以解决问题,因此您无需这样做。

如果不是因为陈旧的锁,那就是你所要做的。然而,事情发生了,程序中止,并不总是删除他们的锁。所以实现可以更精细一些。

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

man -s 2 mkdir

EXECUTION_CONTROL_FILE:是名称PATH和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