1########################################################################
2#                                                                      #
3#               This software is part of the ast package               #
4#          Copyright (c) 1982-2013 AT&T Intellectual Property          #
5#                      and is licensed under the                       #
6#                 Eclipse Public License, Version 1.0                  #
7#                    by AT&T Intellectual Property                     #
8#                                                                      #
9#                A copy of the License is available at                 #
10#          http://www.eclipse.org/org/documents/epl-v10.html           #
11#         (with md5 checksum b35adb5213ca9657e911e9befb180842)         #
12#                                                                      #
13#              Information and Software Systems Research               #
14#                            AT&T Research                             #
15#                           Florham Park NJ                            #
16#                                                                      #
17#                    David Korn <dgkorn@gmail.com>                     #
18#                                                                      #
19########################################################################
20
21float DELAY=0.2
22integer FOREGROUND=10 BACKGROUND=2
23
24s=$($SHELL -c '
25integer i foreground=0 background=0
26float delay='$DELAY' d=0 s=0
27
28set --errexit
29
30trap "(( background++ ))" CHLD
31
32(( d = delay ))
33for ((i = 0; i < '$BACKGROUND'; i++))
34do
35    sleep $d &
36    (( d *= 4 ))
37    (( s += d ))
38done
39
40for ((i = 0; i < '$FOREGROUND'; i++))
41do
42    (( foreground++ ))
43    sleep $delay
44    (( s -= delay ))
45    $SHELL -c : > /dev/null # foreground does not generate SIGCHLD
46done
47
48if (( (s += delay) < 1 ))
49then
50    (( s = 1 ))
51fi
52
53sleep $s
54wait
55print foreground=$foreground background=$background
56') || log_error "test loop failed"
57
58eval $s
59
60(( foreground == FOREGROUND )) || log_error "expected '$FOREGROUND foreground' -- got '$foreground' (DELAY=$DELAY)"
61(( background == BACKGROUND )) || log_error "expected '$BACKGROUND background' -- got '$background' (DELAY=$DELAY)"
62
63set --noerrexit
64
65if [[ ${.sh.version} == Version?*([[:upper:]])J* ]]
66then
67    jobmax=4
68    got=$($SHELL -c '
69        JOBMAX='$jobmax' JOBCOUNT=$(('$jobmax'*2))
70        integer running=0 maxrunning=0
71        trap "((running--))" CHLD
72        for ((i=0; i<JOBCOUNT; i++))
73        do
74            sleep 1 &
75            sleep .1
76            if ((++running > maxrunning))
77            then
78                ((maxrunning=running))
79            fi
80
81        done
82
83        wait
84        print running=$running maxrunning=$maxrunning
85    ')
86    exp='running=0 maxrunning='$jobmax
87    [[ $got == $exp ]] || log_error "SIGCHLD trap queueing failed -- expected '$exp', got '$got'"
88
89    got=$($SHELL -c '
90        typeset -A proc
91
92        trap "
93            print \${proc[\$!].name} \${proc[\$!].status} \$?
94            unset proc[\$!]
95        " CHLD
96
97        { sleep 3; print a; exit 1; } &
98        proc[$!]=( name=a status=1 )
99
100        { sleep 2; print b; exit 2; } &
101        proc[$!]=( name=b status=2 )
102
103        { sleep 1; print c; exit 3; } &
104        proc[$!]=( name=c status=3 )
105
106        while (( ${#proc[@]} ))
107        do
108            sleep -s
109        done
110    ')
111    exp='c\nc 3 3\nb\nb 2 2\na\na 1 1'
112    [[ $got == $exp ]] || log_error "SIGCHLD trap queueing failed -- expected $(printf %q "$exp"), got $(printf %q "$got")"
113fi
114
115{
116got=$( ( sleep 1;print $'\n') | $SHELL -c 'function handler { : ;}
117    trap handler CHLD; sleep .3 & IFS= read; print good')
118} 2> /dev/null
119[[ $got == good ]] || log_error 'SIGCLD handler effects read behavior'
120
121set -- $(
122    (
123    $SHELL -xc $'
124        trap \'wait $!; print $! $?\' CHLD
125        { sleep 0.1; exit 9; } &
126        print $!
127        sleep 0.5
128    '
129    ) 2>/dev/null; print $?
130)
131if (( $# != 4 ))
132then
133    log_error "CHLD trap failed -- expected 4 args, got $#"
134elif (( $4 != 0 ))
135then
136    log_error "CHLD trap failed -- exit code $4"
137elif (( $1 != $2 ))
138then
139    log_error "child pid mismatch -- got '$1' != '$2'"
140elif (( $3 != 9 ))
141then
142    log_error "child status mismatch -- expected '9', got '$3'"
143fi
144
145trap '' CHLD
146integer d
147for ((d=0; d < 2000; d++))
148do
149    if print foo | grep bar
150    then
151        break
152    fi
153done
154
155(( d==2000 )) ||  log_error "trap '' CHLD  causes side effects d=$d"
156trap - CHLD
157
158x=$($SHELL 2> /dev/null -ic '/bin/notfound; sleep .5 & sleep 1;jobs')
159[[ $x == *Done* ]] || log_error 'SIGCHLD blocked after notfound'
160x=$($SHELL 2> /dev/null  -ic 'kill -0 12345678901234567876; sleep .5 & sleep 1;jobs')
161[[ $x == *Done* ]] || log_error 'SIGCHLD blocked after error message'
162print 'set -o monitor;sleep .5 & sleep 1;jobs' > $TEST_DIR/foobar
163chmod +x $TEST_DIR/foobar
164x=$($SHELL  -c "echo | $TEST_DIR/foobar")
165[[ $x == *Done* ]] || log_error 'SIGCHLD blocked for script at end of pipeline'
166
167tmpfile=$TEST_DIR/file
168$SHELL > $tmpfile <<- \EOF
169	trap 'printf "%d %d %s\n" .sh.sig.pid $! "${.sh.sig.code}"' CHLD
170	{
171		for ((i=0 ; i < 10 ; i++ )) ; do
172			sleep .4
173		done
174
175	} &
176	cpid=$!
177	sleep .2 &
178	print $cpid $!
179	sleep 1
180	kill -STOP $cpid
181	sleep 1
182	kill -CONT $cpid
183	sleep 1
184	wait
185EOF
186
187# Check the output of the previous code to determine if the platform supports reporting SIGCONT via
188# waitpid() or wait4(). Ideally this would be done in a manner that doesn't result in false
189# negatives but it is not obvious how to do that. See issue #561.
190if grep -q CONTINUED $tmpfile
191then
192    expected_statuses=(EXITED STOPPED CONTINUED EXITED)
193else
194    # The platform does not support reporting SIGCONT via waitpid() or wait4().
195    expected_statuses=(EXITED STOPPED EXITED)
196fi
197
198{
199    read xpid pid
200    for stat in $expected_statuses
201    do
202        read pid1 pid2 status  || { log_error "line with stopped continued or exited expected";break;}
203        [[ $pid1 == $pid ]] || log_error ".sh.sig.pid=$pid1 should be $pid"
204        [[ $pid2 == $pid ]] ||  log_error "\$!=$pid1 should be $pid"
205        [[ $status == $stat ]] || log_error "status is $status, should be $stat"
206        pid=$xpid
207    done
208
209} < $tmpfile
210
211# ==========
212# Verify we can trap the termination signal for every job we put in the background.
213#
214typeset -A pids
215function sighandler_chld
216{
217    if [[ ${pids[${.sh.sig.pid}]} ]]
218    then
219        unset -v pids[${.sh.sig.pid}]
220    else
221        log_error "${.sh.sig.pid} not recognized as a sleep command put in the background"
222        trap - CHLD
223    fi
224}
225
226trap 'sighandler_chld' CHLD
227integer i
228typeset -i n_jobs=100
229for (( i=1 ; i <= n_jobs ; i++ ))
230do
231    # The multiplication is to slightly stagger the exit time of the sleeps to make it more likely
232    # we'll get a SIGCHLD for each one.
233    sleep $(( i * 0.01 )) &
234    pids[$!]=1
235done
236
237wait
238#
239# We would like to test that $pids is empty. But we can't because most UNIX implementations do not
240# queue new instances of a signal while the handler for the signal is running. This means that it is
241# likely that the SIGCHLD generated by some exiting `sleep` commands above will be dropped on the
242# floor by the kernel. And therefore `sighandler_chld` will not have been run for some of the jobs.
243# But it should be run for the majority of them so verify that is the case.
244#
245# TODO: Decrease the expected value from 85% to 30% or less when issue #735 is fixed.
246expect=$(( n_jobs * 85 / 10 ))
247actual=${#pids[*]}
248(( actual <= expect )) || \
249    log_error "too many jobs missed by sighandler_chld" "$expect" "$actual  $(typeset -p pids)"
250