|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Copyright AppsCode Inc. and Contributors |
| 4 | +# |
| 5 | +# Licensed under the AppsCode Community License 1.0.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | + |
| 17 | +set -euo pipefail |
| 18 | + |
| 19 | +MAX_WORKERS=${MAX_WORKERS:-30} |
| 20 | +MAX_RETRIES=${MAX_RETRIES:-10} |
| 21 | +TIMEOUT=${TIMEOUT:-300} |
| 22 | +FAIL_LOG=$(mktemp) |
| 23 | +declare -a ASYNC_PIDS=() |
| 24 | + |
| 25 | +FIFO=$(mktemp) |
| 26 | +rm -f "$FIFO" |
| 27 | +mkfifo "$FIFO" |
| 28 | +exec 3<>"$FIFO" |
| 29 | +rm -f "$FIFO" |
| 30 | + |
| 31 | +for ((i = 0; i < MAX_WORKERS; i++)); do |
| 32 | + echo >&3 |
| 33 | +done |
| 34 | + |
| 35 | +cleanup() { |
| 36 | + trap - EXIT INT TERM |
| 37 | + local pid |
| 38 | + exec 3>&- 2>/dev/null || true |
| 39 | + for pid in "${ASYNC_PIDS[@]:-}"; do |
| 40 | + kill "$pid" 2>/dev/null || true |
| 41 | + done |
| 42 | + for pid in $(jobs -pr); do |
| 43 | + kill "$pid" 2>/dev/null || true |
| 44 | + done |
| 45 | + rm -f "$FAIL_LOG" |
| 46 | +} |
| 47 | +trap cleanup EXIT INT TERM |
| 48 | + |
| 49 | +# Usage: run_async command [args...] |
| 50 | +run_async() { |
| 51 | + local -a cmd=("$@") |
| 52 | + local desc="${cmd[*]}" |
| 53 | + |
| 54 | + read -r -u 3 |
| 55 | + |
| 56 | + ( |
| 57 | + trap 'echo >&3' EXIT |
| 58 | + |
| 59 | + local out exit_code attempt=1 |
| 60 | + while [[ $attempt -le $MAX_RETRIES ]]; do |
| 61 | + out=$(timeout "$TIMEOUT" "${cmd[@]}" 2>&1) && { |
| 62 | + printf '\033[32m✓ %s\033[0m\n' "$desc" >&2 |
| 63 | + exit 0 |
| 64 | + } |
| 65 | + exit_code=$? |
| 66 | + ((attempt++)) |
| 67 | + [[ $attempt -gt $MAX_RETRIES ]] && break |
| 68 | + printf '\033[38;5;208m⚠ %s (attempt %d/%d, exit %d)\n%s\033[0m\n' "$desc" "$attempt" "$MAX_RETRIES" "$exit_code" "$out" >&2 |
| 69 | + sleep $((attempt * 2 + RANDOM % (3 + attempt * 2))) |
| 70 | + done |
| 71 | + printf '\033[31m✗ %s (failed, exit %d)\n%s\033[0m\n' "$desc" "$exit_code" "$out" >&2 |
| 72 | + { |
| 73 | + flock 200 |
| 74 | + printf '%s\nexit=%d\n%s\n---\n' "$desc" "$exit_code" "$out" >&200 |
| 75 | + } 200>>"$FAIL_LOG" |
| 76 | + exit 1 |
| 77 | + ) & |
| 78 | + ASYNC_PIDS+=("$!") |
| 79 | +} |
| 80 | + |
| 81 | +# Wait for all jobs, print summary, return 1 if any failed |
| 82 | +wait_all() { |
| 83 | + local pid |
| 84 | + for pid in "${ASYNC_PIDS[@]}"; do |
| 85 | + wait "$pid" || true |
| 86 | + done |
| 87 | + ASYNC_PIDS=() |
| 88 | + |
| 89 | + echo "---" >&2 |
| 90 | + if [[ -s "$FAIL_LOG" ]]; then |
| 91 | + printf '\033[31mFailed tasks:\033[0m\n' >&2 |
| 92 | + cat "$FAIL_LOG" >&2 |
| 93 | + return 1 |
| 94 | + fi |
| 95 | + printf '\033[32mAll tasks completed successfully\033[0m\n' >&2 |
| 96 | +} |
0 commit comments