Skip to content

Commit

Permalink
Don't block on background tasks (fixes sstephenson#205)
Browse files Browse the repository at this point in the history
adds regression test and example helper (close_non_std_fds)

- use /proc (Linux), lsof (Mac) or procstat (BSD) to get open FDs
- avoid BASHPID (not availbale on Bash3)
  • Loading branch information
martin-schulze-vireso committed Dec 25, 2021
1 parent 172580d commit 130c21c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
7 changes: 7 additions & 0 deletions test/bats.bats
Original file line number Diff line number Diff line change
Expand Up @@ -1305,3 +1305,10 @@ EOF
}
check_no_new_variables
}

@test "Don't wait for disowned background jobs to finish because of open FDs (#205)" {
SECONDS=0
run -0 bats --show-output-of-passing-tests --tap "${FIXTURE_ROOT}/issue-205.bats"
echo $SECONDS
[ $SECONDS -lt 10 ]
}
78 changes: 78 additions & 0 deletions test/fixtures/bats/issue-205.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bats

function bgfunc {
close_non_std_fds
sleep 10
echo "bgfunc done"
return 0
}

function get_open_fds() {
if [[ -d /proc/$$/fd ]]; then # Linux
read -d '' -ra open_fds < <(ls -1 /proc/$$/fd) || true
elif command -v lsof >/dev/null ; then # MacOS
local -a fds
IFS=$'\n' read -d '' -ra fds < <(lsof -F f -p $$) || true
open_fds=()
for fd in "${fds[@]}"; do
case $fd in
f[0-9]*) # filter non fd entries (mainly pid?)
open_fds+=("${fd#f}") # cut off f prefix
;;
esac
done
elif command -v procstat >/dev/null ; then # BSDs
local -a columns header
{
read -r -a header
local fd_column_index=-1
for ((i=0; i<${#header[@]}; ++i)); do
if [[ ${header[$i]} == *FD* ]]; then
fd_column_index=$i
break
fi
done
if [[ $fd_column_index -eq -1 ]]; then
printf "Could not find FD column in procstat" >&2
exit 1
fi
while read -r -a columns; do
local fd=${columns[$fd_column_index]}
if [[ $fd == [0-9]* ]]; then # only take up numeric entries
open_fds+=("$fd")
fi
done
} < <(procstat fds $$)
else
# TODO: MSYS (Windows)
printf "Neither FD discovery mechanism available\n" >&2
exit 1
fi
}

function close_non_std_fds() {
get_open_fds
for fd in "${open_fds[@]}"; do
if [[ $fd -gt 2 ]]; then
printf "Close %d\n" "$fd"
eval "exec $fd>&-"
else
printf "Retain %d\n" "$fd"
fi
done
}

function otherfunc {
bgfunc &
PID=$!
disown
return 0
}

@test "min bg" {
echo "sec: $SECONDS"
otherfunc
sleep 1 # leave some space for the background job to print/fail early
kill -s 0 -- $PID # fail it the process already finished due to error!
echo "sec: $SECONDS"
}

0 comments on commit 130c21c

Please sign in to comment.