【问题标题】:Prevent race condition when creating lock file创建锁定文件时防止竞争条件
【发布时间】:2019-02-15 21:34:24
【问题描述】:

我的脚本不能同时运行多次。因此它会创建一个锁定文件,并在退出之前将其删除。它会在开始工作之前检查锁定文件是否不存在。

一种非常常见的锁定方法是something like this

function setupLockFile() {
  if (set -o noclobber; echo "lock" > "$lockfile") 2>/dev/null; then
    trap "rm -f $lockfile; exit $?" INT TERM EXIT
  else
    echo "Script running... exiting!" 
    exit 1
  fi
}

但是存在竞争条件 - 如果文件不存在,if 会创建文件,并且脚本可以在定义 trap 之前终止。那么lockfile就不会被删除了。

那么什么是安全的方法呢?

【问题讨论】:

    标签: linux bash shell locking


    【解决方案1】:

    这不是一场竞赛——这是对失败的适应力。如果脚本在删除文件之前就死掉了,您需要手动清理。

    尝试自动执行此清理的常用方法是从任何现有文件中读取 PID,测试该进程是否仍然存在,如果不存在则基本上忽略它的存在。不幸的是,没有一个原子比较和设置操作,这不是微不足道的,因为它引入了一个新的竞争,在读取 PID 和其他人试图忽略它的存在之间。

    查看this question,了解有关仅使用文件系统锁定的更多想法。

    我的建议是要么将锁定文件存储在临时文件系统上(/var/run 通常是 tmpfs 以允许 pidfile 在重新启动时安全消失),以便在重新启动后自行修复,或者让脚本举手并要求人工干预。可靠地处理每一个失败案例会增加复杂性,因此可能会比寻求人类帮助带来更多的失败概率。

    复杂性不仅仅存在于今天,它还存在于代码的整个生命周期中。完成后可能是正确的,但下一个人会打破它吗?

    【讨论】:

      【解决方案2】:

      首先检查lockfile,然后trap,然后写入:

      function setupLockFile() {
        if [ -f "$lockfile" ]; then
          echo "Script running... exiting!" 
          exit 1
        else trap "rm -f $lockfile; exit $?" INT TERM EXIT
             set -o noclobber; echo "lock" > "$lockfile" || exit 1
        fi
      }
      

      还有一种“官方”方法可以使用 flock 命令检查锁定文件,该命令是 util-linux 的一部分。

      【讨论】:

      • 如果脚本崩溃,您将进入脚本永远不会再次启动的永久故障状态。 IMO 这很好,但这不是 OP 想要实现的目标。
      • 从 bash 中调用 flock(2) 玩得开心 :)
      • 操作想要“脚本可以在定义陷阱之前终止”。
      • 他可以以./script;rm "$lockfile"运行脚本,那么即使崩溃也会删除lockfile。
      【解决方案3】:

      让我们尝试另一种方法:

      • 在创建锁定文件之前设置陷阱
      • 将 PID 存储在锁定文件中
      • 让陷阱检查当前实例的 PID 是否匹配锁文件中的任何内容

      例如:

      trap "cleanUp" INT TERM EXIT
      
      function cleanUp {
        if [[ $$ -eq $(<$lockfile) ]]; then
          rm -f $lockfile
          exit $?
        fi
      }
      
      function setupLockFile {
        if ! (set -o noclobber; echo "$$" > "$lockfile") 2>/dev/null; then
          echo "Script running... exiting!"
          exit 1
        fi
      }
      

      这样,您可以将检查锁定文件的存在及其创建作为单个操作,同时还可以防止陷阱删除先前运行的实例的锁定文件。

      此外,正如我在下面的 cmets 中提到的,如果锁定文件已经存在,我建议检查具有给定 PID 的进程是否正在运行。 因为您永远不知道无论出于何种原因,锁定文件是否仍会在磁盘上保持孤立状态。 因此,如果您想减少手动删除孤立锁字段的需要,您可以添加额外的逻辑来检查 PID 是否是孤立的。

      例如 - 如果没有从锁定文件中找到具有给定 PID 的正在运行的进程,您可以假设这是来自先前实例的孤立锁定文件,您可以用当前 PID 覆盖它并继续。 如果找到一个进程,你可以比较它的名字,看看它是否真的是同一脚本的另一个实例 - 如果不是,你可以覆盖锁定文件中的 PID 并继续。

      为了简单起见,我没有在代码中包含这个,如果你愿意,你可以尝试自己创建这个逻辑。 :)

      【讨论】:

      • 您的代码在检查文件是否存在和决定覆盖它之间存在竞争。这就是 OP 的代码旨在防止的。
      • 我理解他的请求的方式是,他希望避免在定义陷阱之前创建锁定文件,以便脚本不能留下孤立的锁定文件。这就是我的示例解决的问题。但是,是的,我明白你在暗示什么。
      • 这个想法是为了防止一些脚本的并发运行 - 并避免竞争条件,并避免孤立文件。
      • 是的,你是对的。我想更好的方法是将当前 PID 存储到锁文件中,然后在陷阱中增加清理以检查它正在删除的锁文件是否属于脚本的当前实例。这样,您可以在 if 条件中创建锁定文件之前定义陷阱,而不必担心会删除另一个正在运行的实例的锁定文件。
      • 我已经重写了我的答案,请检查并告诉我你的想法。够防弹吗? :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 2014-05-27
      相关资源
      最近更新 更多