我一直在尝试改进一些 shell 代码,以实现破坏的锁定机制。
我的想法是通过在文件上调用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
我已经进行了一些并发 call ,并为此投入了很多精力,但并没有遇到失败。
但这实际上是安全的吗?
最佳答案
不安全
假设脚本的三个副本(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并退出。