即使遇到'wait',也会遇到比赛条件



我在bash程序中遇到了一个奇怪的竞赛条件。我试着通过一个足够简单的演示程序复制它,但很明显,对于所有/大多数与时间相关的比赛演示尝试,我都做不到。

以下是该程序的抽象版本,它不会重复这个问题,但让我仍然解释一下:

# Abstracted version of the original program
# that is NOT able to demo the race.
#
function foo() {
local instance=$1
# [A lot of logic here -
#  all foreground commands, nothing in the background.]
echo "$instance: test" > /tmp/foo.$instance.log        
echo "Instance $instance ended"
}
# Launch the process in background...
#
echo "Launching instance 1"
foo 1 &
# ... and wait for it to complete.
#
echo "Waiting..."
wait
echo "Waiting... done.  (wait exited with: $?)"
# This ls command ALWAYS fails in the real
# program in the 1st while-iteration, complaining about 
# missing files, but works in the 2nd iteration!
#
# It always works in the very 1st while-iteration of the
# abstracted version.
#
while ! ls -l /tmp/foo.*; do
:
done

在我的原始程序中(而不是在上面的抽象版本中(,我确实在stdout上看到了Waiting... done. (wait exited with: 0),就像我在上面的版本中看到的一样。然而,ls -l总是在原始版本中失败,但在第一次while循环迭代中总是在上述抽象版本中工作。

此外,尽管在stdout上看到Instance 1 ended消息,ls命令还是失败了。输出为:

$ ./myProgram
Launching instance 1
Waiting...
Waiting... done. (wait exited with: 0)
Instance 1 ended
ls: cannot access '/tmp/foo.*': No such file or directory
/tmp/foo.1
$

我注意到,如果在我的原始程序中,在ls之前放一个sleep 1,while循环可以安全地取消,如下所示:

# This too works in the original program:
sleep 1
ls -l /tmp/foo.*

问题:为什么wait在我的原始程序中没有按预期工作?有什么建议至少可以帮助解决问题吗?

我在Ubuntu 18.04上使用bash 4.4.19

编辑:我刚刚验证了原始失败程序中对wait的调用正在退出,状态代码为0

编辑2:Instance 1 ended消息不应该出现在Waiting... done. (wait exited with: 0)之前吗?在bash中处理后台进程时,这可能是操作系统磁盘缓冲区/缓存的"刷新问题"吗?

EDIT 3:如果不是while循环或sleep 1破解,而是发出sync命令,那么,瞧,它就工作了!但为什么我必须在一个程序中做sync,而不是在另一个程序?

我注意到以下三种技巧都有效,但不太确定原因:

破解1

while ! ls -l /tmp/foo.*; do
:
done

黑客2

sleep 1
ls -l /tmp/foo.*

黑客3

sync
ls -l /tmp/foo.*

这可能是操作系统磁盘缓冲区/缓存的"刷新问题"吗?尤其是在处理后台进程时,尤其是在bash中?换句话说,对wait的调用似乎在刷新磁盘缓存之前返回(或者,在操作系统自己实现并完成刷新磁盘缓存之后(。

编辑感谢@Jon,他的猜测非常接近,让我朝着正确的方向思考,还有@chepner古老的、有点明智的调整建议。

真正的问题:我启动foo,并不是像我最初问题中不准确的抽象版本所示的那样直接/明确地启动,而是通过另一个launchThread函数启动的,在做了一些记账之后,它的主体中也会说foo 1 &。对launchThread的调用本身就以&作为后缀!所以,我的wait真的在launchThread上等待,而不是在foo上!sleepsyncwhile只是帮助为foo争取了更多的时间来完成,这就是引入它们起作用的原因。以下是该问题的更准确演示,即使您可能无法在自己的系统上复制它(由于系统之间的调度/时间差异(:

#!/bin/bash -u
function now() {
date +'%Y-%m-%d %H:%M:%S'
}
function log() {
echo "$(now) - $@" >> $logDir/log # Line 1
}
function foo() {
local msg=$1
log "$msg"
echo "  foo ended"
}
function launchThread() {
local f=$1
shift
"$f" "$@" &  # Line 2
}
logDir=/tmp/log
/bin/rm -rf "$logDir"
mkdir -p "$logDir"
echo "Launching foo..."
launchThread foo 'message abc' &  # Line 3
echo "Waiting for foo to finish..."
wait
echo "Waiting for foo to finish... done. (wait exited with: $?)"
ls "$logDir"/log*

上述错误程序的输出:

Launching foo...
Waiting for foo to finish...
Waiting for foo to finish... done. (wait exited with: 0)
foo ended
ls: cannot access '/tmp/log/log*': No such file or directory

如果我从Line 2Line 3中删除&,则程序正常工作,输出如下:

Launching foo...
Waiting for foo to finish...
foo ended
Waiting for foo to finish... done. (wait exited with: 0)
/tmp/log/log

如果我从Line 1中删除$(now)部分,程序也能正常工作。

最新更新