我一直在尝试改进一些外壳代码,以实现破坏的锁定机制。
我的想法是通过在文件上调用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
我已经进行了一些并发呼叫,并为此投入了很多精力,但并没有遇到失败。
但是实际上安全吗?
答案 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)
您的代码并不安全,但可能并非出于您的考虑。
以下是一个可能的顺序,它可能允许一个进程在另一个仍在运行时通过检查:
echo $$ > $PIDFILE
之前的某个点结束。由于某种原因,进程B会在此处短暂停止。导致相同结果的其他可能事件序列也存在。