实现可移植文件锁定机制



我已经按照linux手册页中"open"的建议实现了一个文件锁定机制,该建议指出:

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

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

我试着用谷歌搜索,但我似乎能找到的都是上面提到的"做的方式"。

谁能向我解释一下什么情况会导致链接失败(返回-1),但链接计数增加到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.

创建另一个文件比什么都麻烦。请创建一个目录,然后检查创建结果。Unix手册指出,只有一个任务可以成功创建目录,如果目录已经存在,则另一个任务将失败,包括两个任务同时尝试创建目录的情况。操作系统自己处理这个问题,所以你不必。

如果不是为了可能的过期锁,这就是您所要做的。然而,事情发生了,程序中止并且不总是移除它们的锁。所以实现可以更精细一些。

在一个脚本中,我经常使用下面的代码。它自动处理过期的锁。您可以在c中实现相同的功能。
man -s 2 mkdir

EXECUTION_CONTROL_FILE:是一个名称路径和目录名,类似于/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 "$1n"|cut -c1-2`
  year=`expr $year + 0`
  month=`printf "$1n"|cut -c3-4`
  day=`printf "$1n"|cut -c5-6`
  day=`expr $day - 1`
  hour=`printf "$1n"|cut -c7-8`
  min=`printf "$1n"|cut -c9-10`
  sec=`printf "$1n"|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 "$secn"
}

像这样使用:

    # 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

最新更新