yog*_*212 4 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
Run Code Online (Sandbox Code Playgroud)
我已经进行了一些并发呼叫,并为此投入了很多精力,但并没有遇到失败。
但这实际上是安全的吗?
不安全
假设脚本的三个副本(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
Run Code Online (Sandbox Code Playgroud)
切换到A并让它运行另外两个语句:
echo $$ > $PIDFILE
rm $flag || exit
Run Code Online (Sandbox Code Playgroud)
这成功了;$PIDFILE
现在包含A的PID。
切换到B并让其运行相同的语句。rm
失败,因此B退出,但$PIDFILE
现在包含B的PID。
切换到C。C才刚刚开始运行,因此它要做的第一件事是重新创建$flag
:
PIDFILE=/tmp/test.pid
flag=$PIDFILE.flag
touch $flag
Run Code Online (Sandbox Code Playgroud)
现在进行PID检查:
if [ -f $PIDFILE ]; then
ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi
Run Code Online (Sandbox Code Playgroud)
这样通过是因为$PIDFILE
包含B的PID,但是B不再运行。
现在我们去
echo $$ > $PIDFILE
rm $flag || exit
Run Code Online (Sandbox Code Playgroud)
这也通过了,因为C刚刚重新创建了$flag
文件。
现在我们同时运行A和C,相互竞争以$PIDFILE
再次覆盖。
除此之外,还有一个“误报”问题:
if [ -f $PIDFILE ]; then
ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi
Run Code Online (Sandbox Code Playgroud)
您可能已经过时$PIDFILE
,但是其中包含的PID已被其他进程重用。在这种情况下,您不会遇到竞争(脚本实例过多),而会拒绝服务(脚本实例过多:0)。您的脚本将看到正在运行的进程,恰好恰好具有“错误的” PID并退出。