blob: 56338f53435ce03cb95c887111668283e2df86e5 [file] [log] [blame]
Dan Smith30d9bf92021-01-19 12:10:52 -08001#!/bin/bash
2#
3# Symbolic asynchronous tasks for devstack
4#
5# Usage:
6#
7# async_runfunc my_shell_func foo bar baz
8#
9# ... do other stuff ...
10#
11# async_wait my_shell_func
12#
13
Dan Smith84b328c2021-03-26 07:17:42 -070014DEVSTACK_PARALLEL=$(trueorfalse True DEVSTACK_PARALLEL)
Dan Smith30d9bf92021-01-19 12:10:52 -080015_ASYNC_BG_TIME=0
16
17# Keep track of how much total time was spent in background tasks
18# Takes a job runtime in ms.
19function _async_incr_bg_time {
20 local elapsed_ms="$1"
21 _ASYNC_BG_TIME=$(($_ASYNC_BG_TIME + $elapsed_ms))
22}
23
24# Get the PID of a named future to wait on
25function async_pidof {
26 local name="$1"
27 local inifile="${DEST}/async/${name}.ini"
28
29 if [ -f "$inifile" ]; then
30 iniget $inifile job pid
31 else
32 echo 'UNKNOWN'
33 return 1
34 fi
35}
36
37# Log a message about a job. If the message contains "%command" then the
38# full command line of the job will be substituted in the output
39function async_log {
40 local name="$1"
41 shift
42 local message="$*"
43 local inifile=${DEST}/async/${name}.ini
44 local pid
45 local command
46
47 pid=$(iniget $inifile job pid)
48 command=$(iniget $inifile job command | tr '#' '-')
49 message=$(echo "$message" | sed "s#%command#$command#g")
50
Dan Smith51e38452021-04-14 07:23:10 -070051 echo "[$BASHPID Async ${name}:${pid}]: $message"
Dan Smith30d9bf92021-01-19 12:10:52 -080052}
53
54# Inner function that actually runs the requested task. We wrap it like this
55# just so we can emit a finish message as soon as the work is done, to make
56# it easier to find the tracking just before an error.
57function async_inner {
58 local name="$1"
59 local rc
Dan Smith51e38452021-04-14 07:23:10 -070060 local fifo="${DEST}/async/${name}.fifo"
Dan Smith30d9bf92021-01-19 12:10:52 -080061 shift
62 set -o xtrace
63 if $* >${DEST}/async/${name}.log 2>&1; then
64 rc=0
65 set +o xtrace
66 async_log "$name" "finished successfully"
67 else
68 rc=$?
69 set +o xtrace
70 async_log "$name" "FAILED with rc $rc"
71 fi
72 iniset ${DEST}/async/${name}.ini job end_time $(date "+%s%3N")
Dan Smithaa5c3872021-04-14 14:27:32 -070073 # Block on the fifo until we are signaled to exit by the main process
74 cat $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -080075 return $rc
76}
77
78# Run something async. Takes a symbolic name and a list of arguments of
79# what to run. Ideally this would be rarely used and async_runfunc() would
80# be used everywhere for readability.
81#
82# This spawns the work in a background worker, records a "future" to be
83# collected by a later call to async_wait()
84function async_run {
85 local xtrace
86 xtrace=$(set +o | grep xtrace)
87 set +o xtrace
88
89 local name="$1"
90 shift
91 local inifile=${DEST}/async/${name}.ini
Dan Smithaa5c3872021-04-14 14:27:32 -070092 local fifo=${DEST}/async/${name}.fifo
Dan Smith30d9bf92021-01-19 12:10:52 -080093
94 touch $inifile
95 iniset $inifile job command "$*"
96 iniset $inifile job start_time $(date +%s%3N)
97
98 if [[ "$DEVSTACK_PARALLEL" = "True" ]]; then
Dan Smithaa5c3872021-04-14 14:27:32 -070099 mkfifo $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800100 async_inner $name $* &
101 iniset $inifile job pid $!
102 async_log "$name" "running: %command"
103 $xtrace
104 else
105 iniset $inifile job pid "self"
106 async_log "$name" "Running synchronously: %command"
107 $xtrace
108 $*
109 return $?
110 fi
111}
112
113# Shortcut for running a shell function async. Uses the function name as the
114# async name.
115function async_runfunc {
116 async_run $1 $*
117}
118
Dan Smith51e38452021-04-14 07:23:10 -0700119# Dump some information to help debug a failed wait
120function async_wait_dump {
121 local failpid=$1
122
123 echo "=== Wait failure dump from $BASHPID ==="
124 echo "Processes:"
125 ps -f
126 echo "Waiting jobs:"
127 for name in $(ls ${DEST}/async/*.ini); do
128 echo "Job $name :"
129 cat "$name"
130 done
131 echo "Failed PID status:"
132 sudo cat /proc/$failpid/status
133 sudo cat /proc/$failpid/cmdline
134 echo "=== End wait failure dump ==="
135}
136
Dan Smith30d9bf92021-01-19 12:10:52 -0800137# Wait for an async future to complete. May return immediately if already
138# complete, or of the future has already been waited on (avoid this). May
139# block until the future completes.
140function async_wait {
141 local xtrace
142 xtrace=$(set +o | grep xtrace)
143 set +o xtrace
144
Dan Smithaa5c3872021-04-14 14:27:32 -0700145 local pid rc running inifile runtime fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800146 rc=0
147 for name in $*; do
148 running=$(ls ${DEST}/async/*.ini 2>/dev/null | wc -l)
149 inifile="${DEST}/async/${name}.ini"
Dan Smith51e38452021-04-14 07:23:10 -0700150 fifo="${DEST}/async/${name}.fifo"
Dan Smith30d9bf92021-01-19 12:10:52 -0800151
152 if pid=$(async_pidof "$name"); then
153 async_log "$name" "Waiting for completion of %command" \
Dan Smith51e38452021-04-14 07:23:10 -0700154 "running on PID $pid ($running other jobs running)"
Dan Smith30d9bf92021-01-19 12:10:52 -0800155 time_start async_wait
156 if [[ "$pid" != "self" ]]; then
Dan Smithaa5c3872021-04-14 14:27:32 -0700157 # Signal the child to go ahead and exit since we are about to
158 # wait for it to collect its status.
Dan Smith51e38452021-04-14 07:23:10 -0700159 async_log "$name" "Signaling child to exit"
Dan Smithaa5c3872021-04-14 14:27:32 -0700160 echo WAKEUP > $fifo
Dan Smith51e38452021-04-14 07:23:10 -0700161 async_log "$name" "Signaled"
Dan Smith30d9bf92021-01-19 12:10:52 -0800162 # Do not actually call wait if we ran synchronously
163 if wait $pid; then
164 rc=0
165 else
166 rc=$?
167 fi
168 cat ${DEST}/async/${name}.log
Dan Smithaa5c3872021-04-14 14:27:32 -0700169 rm -f $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800170 fi
171 time_stop async_wait
172 local start_time
173 local end_time
174 start_time=$(iniget $inifile job start_time)
175 end_time=$(iniget $inifile job end_time)
176 _async_incr_bg_time $(($end_time - $start_time))
177 runtime=$((($end_time - $start_time) / 1000))
178 async_log "$name" "finished %command with result" \
179 "$rc in $runtime seconds"
180 rm -f $inifile
181 if [ $rc -ne 0 ]; then
Dan Smith51e38452021-04-14 07:23:10 -0700182 async_wait_dump $pid
Dan Smith30d9bf92021-01-19 12:10:52 -0800183 echo Stopping async wait due to error: $*
184 break
185 fi
186 else
187 # This could probably be removed - it is really just here
188 # to help notice if you wait for something by the wrong
189 # name, but it also shows up for things we didn't start
190 # because they were not enabled.
191 echo Not waiting for async task $name that we never started or \
192 has already been waited for
193 fi
194 done
195
196 $xtrace
197 return $rc
198}
199
200# Check for uncollected futures and wait on them
201function async_cleanup {
202 local name
203
204 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
205 return 0
206 fi
207
208 for inifile in $(find ${DEST}/async -name '*.ini'); do
209 name=$(basename $pidfile .ini)
210 echo "WARNING: uncollected async future $name"
211 async_wait $name || true
212 done
213}
214
215# Make sure our async dir is created and clean
216function async_init {
217 local async_dir=${DEST}/async
218
219 # Clean any residue if present from previous runs
220 rm -Rf $async_dir
221
222 # Make sure we have a state directory
223 mkdir -p $async_dir
224}
225
226function async_print_timing {
227 local bg_time_minus_wait
228 local elapsed_time
229 local serial_time
230 local speedup
231
232 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
233 return 0
234 fi
235
236 # The logic here is: All the background task time would be
237 # serialized if we did not do them in the background. So we can
238 # add that to the elapsed time for the whole run. However, time we
239 # spend waiting for async things to finish adds to the elapsed
240 # time, but is time where we're not doing anything useful. Thus,
241 # we substract that from the would-be-serialized time.
242
243 bg_time_minus_wait=$((\
244 ($_ASYNC_BG_TIME - ${_TIME_TOTAL[async_wait]}) / 1000))
245 elapsed_time=$(($(date "+%s") - $_TIME_BEGIN))
246 serial_time=$(($elapsed_time + $bg_time_minus_wait))
247
248 echo
249 echo "================="
250 echo " Async summary"
251 echo "================="
252 echo " Time spent in the background minus waits: $bg_time_minus_wait sec"
253 echo " Elapsed time: $elapsed_time sec"
254 echo " Time if we did everything serially: $serial_time sec"
255 echo " Speedup: " $(echo | awk "{print $serial_time / $elapsed_time}")
256}