Bash uses tcsetpgrp() to move a program to the foreground, and
conditionally resumes the program with SIGCONT if the program was
stopped. Consider the following script:
$ ( cmd1 ; read X ; cmd2 ) &
$ ...
$ fg
When fg is issued, the background program is either stopped, or running.
The expectation is that fg will move the program to the foreground,
and run the program to completion.
The conditional check before sending SIGCONT opens up the opportunity
for a race. Programs running in the background are stopped with SIGTTIN
if an attempt is made to read from the controlling terminal, or stopped
with SIGTTOU if an attempt is made to write to the controlling terminal
configured with TOSTOP.
The program could be stopping just as the fg command is moving it to
the foreground. This results the surprising appearance that the program
seems to stop just after fg completes.
The test program below illustrates the race.
Invoking the test with arguments 1 0 moves the running test to
the foregound.
+ jobs
[1]- Running (sleep ${1}1; trap 'touch READ ; exit 15' 15; read X) &
[2]+ Running (sleep ${1}1; sleep ${3}2; kill $!) &
+ fg %1
( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
+ trap 'touch READ ; exit 15' 15
+ read X
+ sleep 0.000474502
+ kill 1094194
++ touch READ
++ exit 15
Invoking the test with arguments 0 1 moves the stopped test to
the foreground.
+ jobs
[1]+ Stopped (sleep ${1}1; trap 'touch READ ; exit 15' 15; read X)
[2] Done (sleep ${1}1; sleep ${3}2; kill $!)
+ fg %1
( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
++ touch READ
++ exit 15
Invoking the test without arguments races the fg and read to trigger
the behaviour.
+ jobs
[1]- Running (sleep ${1}1; trap 'touch READ ; exit 15' 15; rea
d X) &
[2]+ Running (sleep ${1}1; sleep ${3}2; kill $!) &
+ fg %1
+ read X
( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
+ echo 149
149
Interestingly dash/ash does not exhibit this behaviour. Looking at the
implementation since 1994 shows that it issues SIGCONT unconditionally
after TIOCSPGRP:
https://svnweb.freebsd.org/base/head/bin/sh/jobs.c?revision=1556&view=markup#l175
https://svnweb.freebsd.org/base/head/bin/sh/jobs.c?revision=1556&view=markup#l207
The prior art suggested by dash/ash is that bash should send SIGCONT
unconditionally after tcsetpgrp() when moving a program to the
foreground.
Earl
#!/bin/sh
set -mbux
race()
{
set -- $1 $2 $(od -An -N2 -i /dev/random)
set -- $1 $2 0.000$3
rm -f READ
( sleep ${1}1 ; trap 'touch READ ; exit 15' 15 ; read X ) &
( sleep ${1}1 ; sleep ${3}2 ; kill $! ) &
sleep ${2}3
jobs
fg %1 || echo $?
[ -e READ ] || {
ps wwwaxjfh
jobs
exit 1
}
wait %1
wait %2
wait %3
}
main()
{
set -- ${1-0} ${2-0}
while : ; do
set -- ${1%.*} ${2%.*}
set -- "$@" $(od -An -N2 -i /dev/random)
set -- "$@" $(od -An -N2 -i /dev/random)
set -- $1.000$3 $2.000$4
race "$@"
done
}
main "$@"