Skip to content

Under --parallel, every worker re-probes the clock: one perl exec per test (202 vs 3 for 200 tests) #1353

Description

@Chemaclass
Q A
OS macOS (arm64)
Shell & version bash 3.2.57
bashunit version 0.50.1

Summary

Under --parallel on a shell without EPOCHREALTIME, every worker re-probes the clock, and the probe forks perl. The probe count scales with the number of tests. It happens even when per-test timing is switched off, because deciding that timing is off is what triggers the probe.

Measured

PATH-shim census over perl, one file of 200 trivial tests, macOS arm64, bash 3.2.57:

Mode perl execs
--no-parallel 3
--parallel 202

One exec per test. A second measurement on the same host counted 404 over 202 distinct test ids, so the exact multiplier depends on settings, but it scales per test either way.

A/B with the impl resolved in the main shell before dispatch, 200 tests, --parallel: 895 ms to 620 ms on macOS. On Linux the probe resolves to date or a plain subshell, so the effect there is noise (3.2: 784 to 771; 5.3: 414 to 394).

Current behavior

bashunit::clock::init (src/util/clock.sh:233-235), called from bashunit:99:

function bashunit::clock::init() {
  _BASHUNIT_START_TIME=$(bashunit::clock::now)
}

bashunit::clock::now resolves the implementation through _choose_impl, which assigns _BASHUNIT_CLOCK_NOW_IMPL (src/util/clock.sh:3, :5-17). That assignment happens inside the command substitution, so it dies with the subshell. The main shell is left with _BASHUNIT_CLOCK_NOW_IMPL still empty.

In a sequential run the first real clock read in the main shell repopulates it once, hence 3 execs. Under --parallel each test runs in a fork that inherits the empty value, so each one re-probes: shell_time fails (no EPOCHREALTIME before 5.0), date +%s%N fails (BSD date has no %N), then perl runs.

The reason this fires with timing off is bashunit::clock::is_expensive (src/util/clock.sh:95). BASHUNIT_SHOW_EXECUTION_TIME=auto consults it (src/config/env.sh:524-529) from needs_test_duration (src/runner/context.sh:132-137), once per test. Answering "is the clock expensive" requires choosing the impl, which forks the interpreter it is trying to avoid.

.claude/rules/perf-fork-budget.md states the parallel budget as "~11 forks for a 10-test file". That figure does not include these, because the census fixture there is sequential.

Expected behavior

Resolve the clock implementation once in the main shell, before any worker is dispatched, and let the forks inherit the resolved value. _choose_impl already seeds the return slot as part of probing (#802), so the fix is to call it outside a command substitution rather than through $( ).

Two related cleanups in the same area:

The RED test is a fork-budget assertion in tests/acceptance/bashunit_run_forks_test.sh: the perl count for a --parallel run must not grow with the number of tests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions