创建锁定文件时防止竞争条件
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了创建锁定文件时防止竞争条件相关的知识,希望对你有一定的参考价值。
我的脚本不能同时运行多次。因此它会创建一个锁定文件,并在退出之前将其删除。它在开始工作之前检查锁文件是否不存在。
一种非常常见的锁定方法是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
之前终止脚本。然后不会删除锁定文件。
那么这样做的安全方法是什么?
这不是一场竞赛 - 它对失败的抵御能力。如果脚本在删除文件之前死亡,则需要手动清理。
尝试自动执行此清理的常用方法是从任何现有文件中读取PID,测试以查看该进程是否仍然存在,并且如果不存在则基本上忽略它的存在。遗憾的是,没有原子比较和设置操作,这是非常简单的,因为它引入了一个新的种族,在读取PID和其他人试图忽略它的存在之间。
查看this question,了解有关仅使用文件系统进行锁定的更多建议。
我的建议是将锁文件存储在临时文件系统上(/var/run
通常是tmpfs,以允许pidfiles在重新启动时安全地消失)以便在重新启动后自行修复,或者让脚本举手并请求手动干预。处理每个故障情况可靠地增加了复杂性,因此可能引入更多的失败概率而不是向人寻求帮助。
复杂性不仅仅是今天,而是代码的生命周期。当你完成它可能是正确的,但下一个人会打破它吗?
让我们尝试另一种方法:
- 在创建锁定文件之前设置陷阱
- 将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
}
这样,您可以检查锁定文件是否存在,并将其创建为单个操作,同时还可以防止陷阱删除先前运行的实例的锁定文件。
另外,正如我在下面的评论中提到的,如果锁文件已经存在,我建议检查具有给定PID的进程是否正在运行。因为你永远都不知道锁文件是否仍然可以在磁盘上保持孤立状态。因此,如果您想减少手动删除孤立锁定fiels的需要,可以添加其他逻辑来检查PID是否是孤立的。
例如 - 如果没有找到来自锁定文件的给定PID的正在运行的进程,则可以假设这是来自先前实例的孤立锁定文件,并且您可以使用当前PID覆盖它并继续。如果找到进程,则可以比较其名称以查看它是否确实是同一脚本的另一个实例 - 如果不是,则可以覆盖锁定文件中的PID并继续。
我没有在代码中包含这个以保持简单,如果你愿意,你可以尝试自己创建这个逻辑。 :)
首先检查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命令来检查lockfiles,这是util-linux的一部分。
以上是关于创建锁定文件时防止竞争条件的主要内容,如果未能解决你的问题,请参考以下文章
我是否必须锁定Blueprint实例以避免Flask中的竞争条件?
Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题