Skip to content

Commit f81c7e4

Browse files
authored
perf(runner): use 'wait -n' for parallel job slot on Bash 4.3+ (#671)
1 parent a3de42b commit f81c7e4

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Performance
66
- Faster runtime-error detection: single `case` glob instead of 23-iteration loop in `detect_runtime_error` (#668)
77
- Hot-path coverage flag now cached in `_BASHUNIT_COVERAGE_ON`, removing a function dispatch per call (#664)
8+
- Parallel runner blocks on `wait -n` on Bash 4.3+ instead of polling `jobs -r`, removing sleep-induced slot-release latency (#667)
89

910
## [0.36.0](https://github.com/TypedDevs/bashunit/compare/0.35.0...0.36.0) - 2026-05-07
1011

src/runner.sh

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,35 @@ function bashunit::runner::print_verbose_test_summary() {
141141
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '-'
142142
}
143143

144+
# Returns 0 when this Bash supports `wait -n` (Bash 4.3+), 1 otherwise.
145+
function bashunit::runner::_supports_wait_n() {
146+
local major="${BASH_VERSINFO[0]:-0}"
147+
local minor="${BASH_VERSINFO[1]:-0}"
148+
if [ "$major" -gt 4 ]; then
149+
return 0
150+
fi
151+
if [ "$major" -eq 4 ] && [ "$minor" -ge 3 ]; then
152+
return 0
153+
fi
154+
return 1
155+
}
156+
144157
function bashunit::runner::wait_for_job_slot() {
145158
local max_jobs="${BASHUNIT_PARALLEL_JOBS:-0}"
146159
if [ "$max_jobs" -le 0 ]; then
147160
return 0
148161
fi
149162

150-
# Adaptive backoff: start at 50ms, grow to 200ms to reduce `jobs -r` overhead
151-
# on long-running tests while keeping short tests responsive.
163+
if bashunit::runner::_supports_wait_n; then
164+
# Bash 4.3+: block until any child exits. No polling, no sleep latency.
165+
while [ "$(jobs -r | wc -l)" -ge "$max_jobs" ]; do
166+
wait -n 2>/dev/null || break
167+
done
168+
return 0
169+
fi
170+
171+
# Bash 3.x fallback: adaptive poll starting at 50ms, growing to 200ms to
172+
# reduce `jobs -r` overhead on long-running tests while staying responsive.
152173
local delay="0.05"
153174
local iterations=0
154175
while true; do

tests/unit/parallel_test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ function test_wait_for_job_slot_returns_immediately_when_under_limit() {
5555
assert_successful_code "$?"
5656
}
5757

58+
function test_supports_wait_n_matches_running_bash_version() {
59+
local major="${BASH_VERSINFO[0]:-0}"
60+
local minor="${BASH_VERSINFO[1]:-0}"
61+
local expected_rc=1
62+
if [ "$major" -gt 4 ] || { [ "$major" -eq 4 ] && [ "$minor" -ge 3 ]; }; then
63+
expected_rc=0
64+
fi
65+
66+
bashunit::runner::_supports_wait_n
67+
assert_same "$expected_rc" "$?"
68+
}
69+
70+
function test_wait_for_job_slot_releases_when_background_job_finishes() {
71+
export BASHUNIT_PARALLEL_JOBS=1
72+
73+
# Launch a short-lived job, occupy the only slot, then call wait_for_job_slot.
74+
# On Bash 4.3+ this exercises the `wait -n` path; on Bash 3.x the poll path.
75+
(sleep 0.1) &
76+
bashunit::runner::wait_for_job_slot
77+
78+
assert_successful_code "$?"
79+
}
80+
5881
# === is_enabled tests ===
5982

6083
function test_parallel_enabled_on_windows() {

0 commit comments

Comments
 (0)