[PATCH] add rlWait

Hubert Kario hkario at redhat.com
Fri Nov 22 15:09:03 UTC 2013


since the bash builtin `wait' command does not provide any kind of
timeout, we need to wrap it around a method that will allow
for killing the process we wait for

this does not duplicate functionality of rlWatchdog, as it does
use wait builtin and returns the background command exit code

allows to remove some code duplication in __INTERNAL_wait_for_cmd()

Signed-off-by: Hubert Kario <hkario at redhat.com>
---
 src/synchronisation.sh          | 95 +++++++++++++++++++++++++++++++++++------
 src/test/synchronisationTest.sh | 57 +++++++++++++++++++++++++
 2 files changed, 140 insertions(+), 12 deletions(-)

diff --git a/src/synchronisation.sh b/src/synchronisation.sh
index 9320b8c..d29affd 100644
--- a/src/synchronisation.sh
+++ b/src/synchronisation.sh
@@ -57,6 +57,40 @@ __INTERNAL_killtree() {
     return ${_ret:-0}
 }
 
+# wrapper around bash builtin `wait', adds timeout capability
+__INTERNAL_wait() {
+    local timeout=30
+    local sigspec=SIGTERM
+
+    while true ; do
+        case "$1" in
+            -t) timeout="$2"; shift 2
+                ;;
+            -s) sigspec="$2"; shift 2
+                ;;
+            --) shift 1
+                break;
+                ;;
+            *) rlLogError "rlWait: unrecognized option"
+                return 128
+                ;;
+        esac
+    done
+
+    local pids="$@"
+
+    (sleep $timeout && for pid in $pids; do __INTERNAL_killtree $pid "$sigspec"; done)&
+    local watcher=$!
+    disown $watcher
+
+    wait "$@"
+    local my_ret=$?
+
+    __INTERNAL_killtree $watcher SIGKILL 2> /dev/null
+
+    return $my_ret
+}
+
 # Since all "wait for something to happen" utilities are basically the same,
 # use a generic routine that can do all their work
 __INTERNAL_wait_for_cmd() {
@@ -165,34 +199,23 @@ __INTERNAL_wait_for_cmd() {
     local command_pid=$!
 
     # kill command running in background if the timout has elapsed
-    ( sleep $timeout && __INTERNAL_killtree $command_pid SIGKILL) 2>/dev/null &
-    local watcher=$!
-
-    wait $command_pid 2> /dev/null
+    __INTERNAL_wait -t $timeout -s SIGKILL -- $command_pid 2> /dev/null
     local ret=$?
     if [[ $ret -eq 0 ]]; then
-        __INTERNAL_killtree $watcher SIGKILL 2>/dev/null
-        wait $watcher 2> /dev/null
         rlLogInfo "${routine_name}: Wait successful!"
         return 0
     else
         case $ret in
             1)
-                __INTERNAL_killtree $watcher SIGKILL 2>/dev/null
-                wait $watcher 2> /dev/null
                 rlLogWarning "${routine_name}: specified PID was terminated!"
                 ;;
             2)
-                __INTERNAL_killtree $watcher SIGKILL 2>/dev/null
-                wait $watcher 2> /dev/null
                 rlLogWarning "${routine_name}: Max number of test command invocations reached!"
                 ;;
             143|137)
                 rlLogWarning "${routine_name}: Timeout reached"
                 ;;
             *)
-                __INTERNAL_killtree $watcher SIGKILL 2>/dev/null
-                wait $watcher 2> /dev/null
                 rlLogError "${routine_name}: Unknown termination cause! Return code: $ret"
         esac
         return 1
@@ -445,6 +468,54 @@ rlWaitForSocket(){
 }
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# rlWait
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+: <<'=cut'
+=pod
+
+=head3 rlWait
+
+Wrapper around bash builtin `wait' command. See bash_builtins(1) man page.
+Kills the process and all its children if the timeout elapses.
+
+    rlWaitFor [n ...] [-s SIGNAL] [-t time]
+
+=over
+
+=item n
+
+List of PIDs to wait for. They need to be background tasks of current shell.
+See bash_builtins(1) section for `wait' command/
+
+=item -t time
+
+Timeout in seconds (optional, default=30). If the wait isn't successful
+before the time elapses then all specified tasks are killed.
+
+=item -s SIGNAL
+
+Signal used to kill the process, optional SIGTERM by default.
+
+=back
+
+=cut
+
+rlWait() {
+    # that is the GNU extended getopt syntax!
+    local TEMP=$(getopt -o t:s: -n 'rlWait' -- "$@")
+    if [[ $? != 0 ]]; then
+        rlLogError "rlWait: Can't parse command options, terminating..."
+        return 128
+    fi
+
+    eval set -- "$TEMP"
+
+    __INTERNAL_wait "$@"
+
+    return $?
+}
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # AUTHORS
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 : <<'=cut'
diff --git a/src/test/synchronisationTest.sh b/src/test/synchronisationTest.sh
index ddbca1c..a7d81ca 100644
--- a/src/test/synchronisationTest.sh
+++ b/src/test/synchronisationTest.sh
@@ -236,3 +236,60 @@ test_rlWaitForCmdDelay() {
 
     rm -rf $test_dir
 }
+
+test_rlWaitPositive() {
+    local test_dir=$(mktemp -d /tmp/beakerlib-test-XXXXXX)
+
+    (sleep 4; touch ${test_dir}/file; exit 4)&
+
+    rlWait $!
+    ret=$?
+
+    assertTrue "Check if background task executed correctly" "[[ -e ${test_dir}/file ]]"
+    assertTrue "Check if returned value comes from background task" "[[ $ret -eq 4 ]]"
+
+    rm -rf $test_dir
+}
+
+test_rlWaitNoPIDs() {
+    local test_dir=$(mktemp -d /tmp/beakerlib-test-XXXXXX)
+
+    (sleep 4; touch ${test_dir}/file; exit 4)&
+
+    rlWait
+    ret=$?
+
+    assertTrue "Check if background task executed correctly" "[[ -e ${test_dir}/file ]]"
+    # when you `wait' for all tasks (no id specified), then wait always returns 0
+    assertTrue "Check if returned value is correct" "[[ $ret -eq 0 ]]"
+
+    rm -rf $test_dir
+}
+
+test_rlWaitNegative() {
+    local test_dir=$(mktemp -d /tmp/beakerlib-test-XXXXXX)
+
+    (sleep 20; touch ${test_dir}/file; exit 4)&
+
+    rlWait $! -t 1
+    ret=$?
+
+    assertTrue "Check if background task didn't execute" "[[ ! -e ${test_dir}/file ]]"
+    assertTrue "Check if returned value indicates the task was killed" "[[ $ret -eq $((128+15)) ]]"
+
+    rm -rf $test_dir
+}
+
+test_rlWaitKill() {
+    local test_dir=$(mktemp -d /tmp/beakerlib-test-XXXXXX)
+
+    (sleep 20; touch ${test_dir}/file; exit 4)&
+
+    rlWait $! -t 1 -s SIGKILL
+    ret=$?
+
+    assertTrue "Check if background task didn't execute" "[[ ! -e ${test_dir}/file ]]"
+    assertTrue "Check if returned value indicates the task was killed by custom signal" "[[ $ret -eq $((128+9)) ]]"
+
+    rm -rf $test_dir
+}
-- 
1.8.3.1



More information about the beakerlib-devel mailing list