运行 进入竞争状态,即使 'wait'
Running into a race-condition, even with a 'wait'
我在 bash
程序中遇到了一个奇怪的竞争条件。我尝试通过一个足够简单的演示程序复制它,但显然,对于 all/most 计时相关的比赛演示尝试,我做不到。
这是该程序的抽象版本,不会重复该问题,但让我仍然解释一下:
# Abstracted version of the original program
# that is NOT able to demo the race.
#
function foo() {
local instance=
# [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
在我的原始程序中(而不是在上面的抽象版本中),我确实在标准输出上看到 Waiting... done. (wait exited with: 0)
,就像我在上面的版本中看到的一样。然而,ls -l
在原始版本中总是失败,但在第一个 while
循环迭代中总是在上述抽象版本中工作。
此外,尽管在标准输出上看到 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 中的后台进程时,这可能是 'flushing problem' 和 OS' disk-buffer/cache 吗?
编辑 3: 如果不是 while
循环或 sleep 1
黑客,我发出 sync
命令,那么,瞧,有用!但是,为什么我必须在一个程序中执行 sync
而在另一个程序中执行?
我注意到以下三个 hack 都有效,但不太清楚为什么:
技巧 1
while ! ls -l /tmp/foo.*; do
:
done
技巧 2
sleep 1
ls -l /tmp/foo.*
技巧 3
sync
ls -l /tmp/foo.*
这会不会是 'flushing problem' 和 OS' disk-buffer/cache,特别是在处理后台进程时,尤其是在 bash
中?换句话说,对 wait
的调用似乎在它刷新磁盘缓存之前返回(或者,在 OS 自己意识到并完成刷新磁盘缓存之前)。
编辑感谢@Jon,他的猜测非常接近,让我思考正确的方向,以及来自@的古老的、按位调整的建议切普纳。
真正的问题: 我开始 foo
,而不是 directly/plainly,如我在原始问题中不准确的抽象版本所示,而是通过另一个 launchThread
函数,在做一些簿记之后,它的主体也会说 foo 1 &
。对 launchThread
的调用本身带有 &
后缀!所以,我的 wait
真的在 launchThread
而不是 foo
等待! sleep
、sync
和 while
只是在帮助 foo
争取更多的时间来完成,这就是引入它们的原因。以下是对该问题的更准确的演示,即使您可能会或可能不会在您自己的系统上复制它(由于 scheduling/timing 系统间的差异):
#!/bin/bash -u
function now() {
date +'%Y-%m-%d %H:%M:%S'
}
function log() {
echo "$(now) - $@" >> $logDir/log # Line 1
}
function foo() {
local msg=
log "$msg"
echo " foo ended"
}
function launchThread() {
local f=
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 2
或 Line 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)
部分,程序也能正常工作。
我在 bash
程序中遇到了一个奇怪的竞争条件。我尝试通过一个足够简单的演示程序复制它,但显然,对于 all/most 计时相关的比赛演示尝试,我做不到。
这是该程序的抽象版本,不会重复该问题,但让我仍然解释一下:
# Abstracted version of the original program
# that is NOT able to demo the race.
#
function foo() {
local instance=
# [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
在我的原始程序中(而不是在上面的抽象版本中),我确实在标准输出上看到 Waiting... done. (wait exited with: 0)
,就像我在上面的版本中看到的一样。然而,ls -l
在原始版本中总是失败,但在第一个 while
循环迭代中总是在上述抽象版本中工作。
此外,尽管在标准输出上看到 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 中的后台进程时,这可能是 'flushing problem' 和 OS' disk-buffer/cache 吗?
编辑 3: 如果不是 while
循环或 sleep 1
黑客,我发出 sync
命令,那么,瞧,有用!但是,为什么我必须在一个程序中执行 sync
而在另一个程序中执行?
我注意到以下三个 hack 都有效,但不太清楚为什么:
技巧 1
while ! ls -l /tmp/foo.*; do
:
done
技巧 2
sleep 1
ls -l /tmp/foo.*
技巧 3
sync
ls -l /tmp/foo.*
这会不会是 'flushing problem' 和 OS' disk-buffer/cache,特别是在处理后台进程时,尤其是在 bash
中?换句话说,对 wait
的调用似乎在它刷新磁盘缓存之前返回(或者,在 OS 自己意识到并完成刷新磁盘缓存之前)。
编辑感谢@Jon,他的猜测非常接近,让我思考正确的方向,以及来自@的古老的、按位调整的建议切普纳。
真正的问题: 我开始 foo
,而不是 directly/plainly,如我在原始问题中不准确的抽象版本所示,而是通过另一个 launchThread
函数,在做一些簿记之后,它的主体也会说 foo 1 &
。对 launchThread
的调用本身带有 &
后缀!所以,我的 wait
真的在 launchThread
而不是 foo
等待! sleep
、sync
和 while
只是在帮助 foo
争取更多的时间来完成,这就是引入它们的原因。以下是对该问题的更准确的演示,即使您可能会或可能不会在您自己的系统上复制它(由于 scheduling/timing 系统间的差异):
#!/bin/bash -u
function now() {
date +'%Y-%m-%d %H:%M:%S'
}
function log() {
echo "$(now) - $@" >> $logDir/log # Line 1
}
function foo() {
local msg=
log "$msg"
echo " foo ended"
}
function launchThread() {
local f=
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 2
或 Line 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)
部分,程序也能正常工作。