From 130c21cf27c717262a2803cd88b26b4865cd3695 Mon Sep 17 00:00:00 2001 From: Martin Schulze Date: Sun, 5 Dec 2021 07:59:07 +0100 Subject: [PATCH] Don't block on background tasks (fixes #205) 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) --- test/bats.bats | 7 +++ test/fixtures/bats/issue-205.bats | 78 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/fixtures/bats/issue-205.bats diff --git a/test/bats.bats b/test/bats.bats index 45b6a6b9..607efad7 100755 --- a/test/bats.bats +++ b/test/bats.bats @@ -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 ] +} diff --git a/test/fixtures/bats/issue-205.bats b/test/fixtures/bats/issue-205.bats new file mode 100644 index 00000000..b097c92b --- /dev/null +++ b/test/fixtures/bats/issue-205.bats @@ -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" +}