Files
go-config/cmd/envc/test_hang.sh
T
eSliderandCursor 2d674de546 feat(envc): improve root help and add stdin hang repro script
Root usage documents commands; help/-h/--help and unknown-command paths print the same text. test_hang.sh reproduces blocking reads on stdin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 10:22:35 +01:00

70 lines
2.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Reproduce and explain envc "hangs": default input is stdin (-); io.ReadAll
# blocks until EOF. A TTY waits for Ctrl-D; /dev/zero never EOF (infinite read).
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
ENVC="${TMPDIR:-/tmp}/envc-hang-test.$$"
cleanup() { rm -f "$ENVC"; }
trap cleanup EXIT
echo "== build envc -> $ENVC"
go build -o "$ENVC" ./cmd/envc
echo
echo "== 1) expected to finish quickly (YAML on stdin, explicit -)"
printf 'k: 1\n' | "$ENVC" convert --from yaml --to json --input - --output - | head -c 200
echo
echo " (ok)"
echo
echo "== 2) empty stdin should finish quickly (EOF immediately)"
if ! out="$( "$ENVC" convert --from yaml --to json --input - --output - </dev/null 2>&1 )"; then
echo " exit non-zero (often empty YAML parse); stderr/out:"
echo "$out" | sed 's/^/ | /'
echo " (ok — no hang, process terminated)"
else
echo "$out" | sed 's/^/ /'
echo " (ok — no hang)"
fi
echo
echo "== 3) infinite stdin: should hit wall-clock limit (GNU timeout -> 124)"
if ! command -v timeout >/dev/null; then
echo " skip: no 'timeout' in PATH"
else
set +e
timeout -v 2s "$ENVC" convert --from yaml --to json --input - --output - </dev/zero >/dev/null 2>&1
ec=$?
set -e
if [[ "$ec" -eq 124 ]]; then
echo " timeout fired (exit 124) — binary was blocked in read (no EOF on stdin)."
elif [[ "$ec" -eq 137 ]] || [[ "$ec" -eq 143 ]]; then
echo " killed by signal (exit $ec) — still indicates blocked read until SIGKILL/SIGTERM."
else
echo " unexpected exit $ec (expected 124 from GNU timeout on hang)"
exit 1
fi
fi
echo
echo "== why it happens"
cat <<'EOF'
convert/get default to reading the input source once with io.ReadAll.
For --input - (convert) or positional "-" (get), that is os.Stdin until EOF.
- Interactive shell, no pipe: stdin is a TTY — blocked until you press Ctrl-D
or pipe data in.
- /dev/zero (or any endless reader): never EOF — blocked until killed or OOM.
merge uses the same pattern for non-"-" inputs via bytesutil; URL fetches use
a 30s HTTP client timeout (see internal/source.URL).
Fix for users: pass a file or URL (--input path), or pipe finite data into stdin.
EOF
echo
echo "== done"