From 0c6410c6709d6e7cfd54e89079beef1c650039b8 Mon Sep 17 00:00:00 2001 From: Shivam Mathur Date: Fri, 11 Sep 2026 14:20:07 +0530 Subject: [PATCH] Fix Homebrew timeout cleanup and retry propagation --- src/scripts/darwin.sh | 14 +++++--- src/scripts/tools/brew.sh | 76 ++++++++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/scripts/darwin.sh b/src/scripts/darwin.sh index 49018876..7b67a92e 100644 --- a/src/scripts/darwin.sh +++ b/src/scripts/darwin.sh @@ -183,6 +183,7 @@ setup_cached_versions() { # Function to setup PHP 5.6 and newer using Homebrew. add_php() { + local exit_code action=$1 existing_version=$2 suffix="$(get_php_formula_suffix)" @@ -198,14 +199,19 @@ add_php() { fi if [[ "$existing_version" != "false" && -z "$suffix" ]]; then if [ "$action" = "upgrade" ]; then - safe_brew install --only-dependencies "$php_formula" - safe_brew upgrade -f --overwrite "$php_formula" + safe_brew install --only-dependencies "$php_formula" || return $? + safe_brew upgrade -f --overwrite "$php_formula" || return $? else brew unlink "$php_keg" fi else - safe_brew install --only-dependencies "$php_formula" - safe_brew install --skip-link -f --overwrite "$php_formula" 2>/dev/null || safe_brew upgrade -f --overwrite "$php_formula" + safe_brew install --only-dependencies "$php_formula" || return $? + safe_brew install --skip-link -f --overwrite "$php_formula" 2>/dev/null || { + exit_code=$? + # A timeout has exhausted its retries; do not start another build via upgrade. + [ "$exit_code" -ne 124 ] || return "$exit_code" + safe_brew upgrade -f --overwrite "$php_formula" || return $? + } fi brew link --force --overwrite "$php_keg" || (sudo chown -R "$(id -un)":"$(id -gn)" "$brew_prefix" && brew link --force --overwrite "$php_keg") } diff --git a/src/scripts/tools/brew.sh b/src/scripts/tools/brew.sh index 6644ab9e..b2677565 100644 --- a/src/scripts/tools/brew.sh +++ b/src/scripts/tools/brew.sh @@ -55,25 +55,45 @@ get_file_mtime() { fi } -# Function to terminate a process and its direct children. -terminate_process_tree() { +# Function to list descendants before their parents, including separate sessions. +get_process_tree() { local pid=$1 local children child children=$(pgrep -P "$pid" 2>/dev/null || true) - kill -TERM "$pid" >/dev/null 2>&1 || true for child in $children; do - terminate_process_tree "$child" + get_process_tree "$child" + done + echo "$pid" +} + +# Function to detect Homebrew's source-build worker, even with buffered output. +is_brew_building_from_source() { + local pid + for pid in $(get_process_tree "$1"); do + if ps -ww -p "$pid" -o command= 2>/dev/null | grep -qE '/Homebrew/build[.]rb([[:space:]]|$)'; then + return 0 + fi + done + return 1 +} + +# Function to terminate the entire tree captured before any parents can exit. +terminate_process_tree() { + local pids pid + pids=$(get_process_tree "$1") + for pid in $pids; do + kill -TERM "$pid" >/dev/null 2>&1 || true done sleep 2 - kill -KILL "$pid" >/dev/null 2>&1 || true - for child in $children; do - terminate_process_tree "$child" + for pid in $pids; do + kill -KILL "$pid" >/dev/null 2>&1 || true done } # Function to run a command with an inactivity watchdog. run_with_inactivity_watchdog() { local timeout_secs="${SETUP_PHP_BREW_INACTIVITY_TIMEOUT:-180}" + local source_timeout_secs="${SETUP_PHP_BREW_SOURCE_INACTIVITY_TIMEOUT:-1800}" local poll_secs="${SETUP_PHP_BREW_WATCHDOG_POLL:-5}" local tmp_dir stdout_fifo stderr_fifo stdout_log stderr_log timeout_file local command_pid stdout_reader_pid stderr_reader_pid monitor_pid exit_code @@ -93,36 +113,39 @@ run_with_inactivity_watchdog() { ("$@" >"$stdout_fifo" 2>"$stderr_fifo") & command_pid=$! - ( - while IFS= read -r line || [ -n "$line" ]; do - printf '%s\n' "$line" - printf '%s\n' "$line" >>"$stdout_log" - done <"$stdout_fifo" - ) & + tee "$stdout_log" <"$stdout_fifo" & stdout_reader_pid=$! - ( - while IFS= read -r line || [ -n "$line" ]; do - printf '%s\n' "$line" >&2 - printf '%s\n' "$line" >>"$stderr_log" - done <"$stderr_fifo" - ) & + tee "$stderr_log" <"$stderr_fifo" >&2 & stderr_reader_pid=$! ( local last_activity current_activity current_err_activity now + local building_from_source=false was_building_from_source=false active_timeout_secs last_activity=$(get_file_mtime "$stdout_log") current_err_activity=$(get_file_mtime "$stderr_log") [ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity" while kill -0 "$command_pid" >/dev/null 2>&1; do sleep "$poll_secs" + kill -0 "$command_pid" >/dev/null 2>&1 || break + now=$(date +%s) + active_timeout_secs="$timeout_secs" + building_from_source=false + if is_brew_building_from_source "$command_pid"; then + building_from_source=true + active_timeout_secs="$source_timeout_secs" + fi + if [ "$building_from_source" != "$was_building_from_source" ]; then + last_activity="$now" + was_building_from_source="$building_from_source" + fi current_activity=$(get_file_mtime "$stdout_log") [ "$current_activity" -gt "$last_activity" ] && last_activity="$current_activity" current_err_activity=$(get_file_mtime "$stderr_log") [ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity" now=$(date +%s) - if [ $((now - last_activity)) -ge "$timeout_secs" ]; then - printf "\nsetup-php: brew produced no output for %ss; terminating and retrying...\n" "$timeout_secs" >&2 + if [ $((now - last_activity)) -ge "$active_timeout_secs" ]; then + printf "\nsetup-php: brew produced no output for %ss; terminating...\n" "$active_timeout_secs" >&2 : >"$timeout_file" terminate_process_tree "$command_pid" break @@ -131,12 +154,15 @@ run_with_inactivity_watchdog() { ) & monitor_pid=$! - wait "$command_pid" - exit_code=$? + exit_code=0 + wait "$command_pid" || exit_code=$? + # Let timeout cleanup finish killing source-build descendants before retrying. + if [ ! -e "$timeout_file" ]; then + kill "$monitor_pid" >/dev/null 2>&1 || true + fi + wait "$monitor_pid" 2>/dev/null || true wait "$stdout_reader_pid" 2>/dev/null || true wait "$stderr_reader_pid" 2>/dev/null || true - kill "$monitor_pid" >/dev/null 2>&1 || true - wait "$monitor_pid" 2>/dev/null || true if [ -e "$timeout_file" ]; then rm -rf "$tmp_dir"