rm可以用于同步锁定文件检查吗?

时间:2019-06-14 22:09:06

标签: linux shell synchronization race-condition

我一直在尝试改进一些外壳代码,以实现破坏的锁定机制。

我的想法是通过在文件上调用rm来仅允许一个调用者完成同步。

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

echo $$ > $PIDFILE
# this should succeed only for one process
rm $flag || exit
echo $$ > $PIDFILE

我已经进行了一些并发呼叫,并为此投入了很多精力,但并没有遇到失败。

但是实际上安全吗?

2 个答案:

答案 0 :(得分:5)

这不安全。

假设您的脚本的三个副本(A,B和C)同时启动,并且/tmp/test.pid最初不存在。

让A和B完成脚本的初始语句:

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

切换到A并让它运行另外两个语句:

echo $$ > $PIDFILE
rm $flag || exit

这成功了; $PIDFILE现在包含A的PID。

切换到B并让其运行相同的语句。 rm失败,因此B退出了,但是$PIDFILE现在包含了B的PID。

切换到C。C才刚刚开始运行,因此它要做的第一件事就是重新创建$flag

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

现在进行PID检查:

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

通过是因为$PIDFILE包含B的PID,但B不再运行。

现在我们要

echo $$ > $PIDFILE
rm $flag || exit

这也通过了,因为C刚刚重新创建了$flag文件。

现在我们同时运行A和C,相互竞争以再次覆盖$PIDFILE


除此之外,还有一个“误报”问题:

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

您可能有一个过时的$PIDFILE,但是其中包含的PID已被其他进程重用。在这种情况下,您不会遇到竞争(脚本实例过多),而会拒绝服务(脚本实例过多:0)。您的脚本会看到正在运行的进程恰好具有错误的PID,然后退出。

答案 1 :(得分:1)

您的代码并不安全,但可能并非出于您的考虑。

以下是一个可能的顺序,它可能允许一个进程在另一个仍在运行时通过检查:

  1. 两个进程A和B开始运行脚本,并同时在第一个echo $$ > $PIDFILE之前的某个点结束。由于某种原因,进程B会在此处短暂停止。
  2. 进程A将其PID写入pidfile,成功地断开标志文件的链接,再次将其PID写入pidfile ,以采取适当措施,然后继续运行。
  3. 现在,进程B恢复运行。它将其PID写入pidfile,覆盖它,然后无法取消链接标志文件并退出。进程A仍在运行,但是现在pidfile不再包含其PID!
  4. 现在进程C出现了,重新创建了标志文件,注意到pidfile存在但包含进程B的PID(不再运行),因此很高兴地通过了所有检查。

导致相同结果的其他可能事件序列也存在。