diff --git a/Resources/bin/start-cmux-profiling b/Resources/bin/start-cmux-profiling index 5c404182464a..a08f1ba54105 100755 --- a/Resources/bin/start-cmux-profiling +++ b/Resources/bin/start-cmux-profiling @@ -154,6 +154,15 @@ if ! [[ "$duration" =~ ^[0-9]+$ ]] || [ "$duration" -lt 1 ]; then exit 2 fi +# The timeout helpers do arithmetic on these; a non-integer would abort the +# surrounding command instead of timing it out. +for timeout_var in CMUX_PROFILE_SYSTEM_PROFILER_TIMEOUT_SECONDS CMUX_PROFILE_TOOL_VERSION_TIMEOUT_SECONDS CMUX_PROFILE_TOC_TIMEOUT_SECONDS; do + if [ -n "${!timeout_var:-}" ] && ! [[ "${!timeout_var}" =~ ^[0-9]+$ ]]; then + echo "start-cmux-profiling: $timeout_var must be a whole number of seconds" >&2 + exit 2 + fi +done + case "$target_channel" in ""|stable|nightly|staging|dev) ;; @@ -248,14 +257,17 @@ run_output_with_timeout() { local timeout_seconds="$1" shift local output_file="${TMPDIR:-/tmp}/cmux-profile-probe.$$.$RANDOM" - local child_pid elapsed + local child_pid ticks + # Poll in tenths of a second: a whole-second poll cost every probe at least + # 1 s, even an instant one like `xcodebuild -version`. + local limit=$((10#$timeout_seconds * 10)) "$@" > "$output_file" 2>/dev/null & child_pid="$!" - elapsed=0 + ticks=0 while kill -0 "$child_pid" >/dev/null 2>&1; do - if [ "$elapsed" -ge "$timeout_seconds" ]; then + if [ "$ticks" -ge "$limit" ]; then kill "$child_pid" >/dev/null 2>&1 || true sleep 1 kill -9 "$child_pid" >/dev/null 2>&1 || true @@ -263,8 +275,8 @@ run_output_with_timeout() { rm -f "$output_file" return 124 fi - sleep 1 - elapsed=$((elapsed + 1)) + sleep 0.1 + ticks=$((ticks + 1)) done if wait "$child_pid"; then @@ -659,10 +671,12 @@ wait_with_timeout() { local child_pid="$1" local timeout_seconds="$2" local log_file="$3" - local elapsed=0 + local ticks=0 + # Tenths of a second, like run_output_with_timeout. + local limit=$((10#$timeout_seconds * 10)) while kill -0 "$child_pid" >/dev/null 2>&1; do - if [ "$elapsed" -ge "$timeout_seconds" ]; then + if [ "$ticks" -ge "$limit" ]; then echo "Timed out after ${timeout_seconds}s; terminating pid ${child_pid}" >> "$log_file" kill "$child_pid" >/dev/null 2>&1 || true sleep 1 @@ -670,8 +684,8 @@ wait_with_timeout() { wait "$child_pid" >/dev/null 2>&1 || true return 124 fi - sleep 1 - elapsed=$((elapsed + 1)) + sleep 0.1 + ticks=$((ticks + 1)) done wait "$child_pid" diff --git a/tests/test_start_cmux_profiling.sh b/tests/test_start_cmux_profiling.sh index 7a72e02d49c8..404123be59f2 100755 --- a/tests/test_start_cmux_profiling.sh +++ b/tests/test_start_cmux_profiling.sh @@ -182,7 +182,8 @@ if [ "${1:-}" = "xctrace" ] && [ "${2:-}" = "record" ]; then fi if [ "${1:-}" = "xctrace" ] && [ "${2:-}" = "export" ]; then - sleep 5 + # Only the TOC-timeout case asks for a slow export. + sleep "${FAKE_XCTRACE_EXPORT_SECONDS:-0}" exit 0 fi @@ -191,7 +192,7 @@ EOF chmod +x "$fake_bin/xcrun" timeout_out="$TMP_DIR/timeout-out" -HOME="$TMP_DIR" PATH="$fake_bin:$PATH" CMUX_PROFILE_TOC_TIMEOUT_SECONDS=1 "$SCRIPT" \ +HOME="$TMP_DIR" PATH="$fake_bin:$PATH" CMUX_PROFILE_TOC_TIMEOUT_SECONDS=1 FAKE_XCTRACE_EXPORT_SECONDS=5 "$SCRIPT" \ --test-ps-file "$ps_file" \ --channel dev \ --tag dog \ @@ -238,6 +239,19 @@ if grep -Fq "$TMP_DIR/cmux DEV dog.app" "$timeout_out/summary.md" || exit 1 fi +# A timeout that is not a whole number of seconds is refused up front; inside the +# timeout helpers' arithmetic it would abort the command it was meant to time. +for bad_timeout in abc 1.5 5s; do + for timeout_var in CMUX_PROFILE_SYSTEM_PROFILER_TIMEOUT_SECONDS CMUX_PROFILE_TOOL_VERSION_TIMEOUT_SECONDS CMUX_PROFILE_TOC_TIMEOUT_SECONDS; do + rc=0 + bad_err="$(env "$timeout_var=$bad_timeout" "$SCRIPT" --list-targets --test-ps-file "$ps_file" 2>&1 >/dev/null)" || rc=$? + if [ "$rc" -ne 2 ] || [[ "$bad_err" != *"$timeout_var must be a whole number of seconds"* ]]; then + echo "FAIL: $timeout_var=$bad_timeout must be rejected with exit 2, got $rc: $bad_err" >&2 + exit 1 + fi + done +done + failing_system_profiler="$TMP_DIR/failing-system-profiler" cat > "$failing_system_profiler" <<'EOF' #!/usr/bin/env bash @@ -245,7 +259,8 @@ exit 9 EOF chmod +x "$failing_system_profiler" display_failed_out="$TMP_DIR/display-failed-out" -PATH="$fake_bin:$PATH" CMUX_PROFILE_SYSTEM_PROFILER="$failing_system_profiler" "$SCRIPT" \ +# The 1 s export outlives several 0.1 s polls, then succeeds inside the timeout. +PATH="$fake_bin:$PATH" CMUX_PROFILE_SYSTEM_PROFILER="$failing_system_profiler" FAKE_XCTRACE_EXPORT_SECONDS=1 "$SCRIPT" \ --test-ps-file "$ps_file" \ --channel dev \ --tag dog \