【问题标题】:Implementing a portable file locking mechanism实现可移植的文件锁定机制
【发布时间】:2013-02-13 12:37:17
【问题描述】:

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

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

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

我试过用谷歌搜索,但我似乎能找到的只是上面提到的同一个参考,反刍为“它的完成方式”。

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

【问题讨论】:

  • 非常好的问题。我想不出在什么情况下会发生这种情况,除非两个竞争进程同时选择相同的唯一文件名(这显然很糟糕)。可以作为非常老的 NFS 错误的解决方法吗?
  • 您是否需要通过 NFS 创建锁定文件? AFAIK 在大多数情况下,您应该能够使用 flock()lockf()

标签: linux lockfile


【解决方案1】:

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

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

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

man -s 2 mkdir

EXECUTION_CONTROL_FILE:是名称 PATH 和目录名称,类似于 /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

【讨论】:

  • 这看起来很活泼,恕我直言。无论您从mkdir()(顺便说一句,您也可以从使用O_EXCL 创建文件中获得)获得的任何原子性,您稍后在访问时间戳文件时都会丢失。如果您在 shell 脚本中,最好使用 util-linux 中的 flock 命令。
  • 这里的目的是描述一种专门使用脚本语言的技术,并且仍然可以与想要访问相同资源的 C 代码兼容。使用“mkdir”并检查返回码就足够了,但这不会处理过时的锁。如果您的锁定机制仅由 C 代码使用,那么您可以使用更好的功能,我同意,这将更加痛苦。我想知道为什么在 Unix 中可用的所有工具中,没有编写(或流行)工具来操作脚本中的锁。
  • 确实存在用于在脚本中操作锁的工具,正如我之前提到的,util-linux 包中的flock 实用程序可以做到这一点。另见stackoverflow.com/a/1985512
【解决方案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.

【讨论】:

    猜你喜欢
    • 2010-12-19
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-14
    • 2021-12-03
    • 1970-01-01
    相关资源
    最近更新 更多