fix(build): исключить SIGPIPE из релизных гейтов под pipefail
Поиск с флагом -q прекращает чтение на первом совпадении и закрывает свой конец
канала. Продюсер, которому осталось что писать, получает SIGPIPE и завершается
кодом 141, а `set -o pipefail` делает 141 статусом всей конструкции:
совпадение НАЙДЕНО -> продюсер оборван -> статус 141 -> «не найдено»
Для утвердительных проверок это ложный FAIL. Для отрицательных — «такой
конструкции в коде нет» — ложный PASS: запрещённая конструкция найдена, а гейт
зелёный. Отрицательными проверками закреплена половина инвариантов приёмки,
включая запрет обхода тестов и запрет `pnpm audit --prod`.
Порог резкий: пока вывод продюсера помещается в буфер канала (64 KiB на Linux),
он не блокируется и успевает завершиться раньше, чем потребитель начнёт читать.
Замер, 60 прогонов на размер: до 60 KiB — 0 отказов, ровно на 64 KiB — 58/60,
от 96 KiB — 60/60. То есть проверка выглядит исправной ровно до первого
источника крупнее буфера, а такие файлы в репозитории уже есть.
- 56 мест переведены на here-string: `grep -q PATTERN <<<"$content"`;
- продюсеры-команды (ss|awk, dpkg-query, /proc/cpuinfo, systemctl
list-unit-files) сначала читаются в переменную;
- введён code_has: десять отрицательных сканов держались на `|| true` внутри
code_without_comments, гасившем 141, — то есть на побочном эффекте
подавления ошибок, а не на заявленном свойстве;
- несуществующий путь в скане больше не означает успех: `2>/dev/null || true`
превращал опечатку в пустой вывод, а пустой вывод для проверки «этого в коде
нет» — это PASS. Проверка явная, а не через set -e: в контексте `! code_has`
bash отключает errexit на весь вызов;
- возврат пайплайна запрещён отдельной приёмкой.
This commit is contained in:
@@ -30,7 +30,7 @@ HYSTERIA_GATE_TIMEOUT_SECONDS="${HYSTERIA_GATE_TIMEOUT_SECONDS:-15}"
|
||||
|
||||
validate_hysteria_version_format() {
|
||||
local value="$1"
|
||||
printf '%s' "$value" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||||
grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' <<<"$value" \
|
||||
|| fail "invalid Hysteria version format: $value (expected vX.Y.Z)"
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ validate_hysteria_sha256_format() {
|
||||
fail "HYSTERIA_ARTIFACT_SHA256 must be a real release sha256"
|
||||
;;
|
||||
esac
|
||||
printf '%s' "$value" | grep -Eq '^[a-fA-F0-9]{64}$' \
|
||||
grep -Eq '^[a-fA-F0-9]{64}$' <<<"$value" \
|
||||
|| fail "HYSTERIA_ARTIFACT_SHA256 must be a 64-char hex SHA256, got: $value"
|
||||
}
|
||||
|
||||
@@ -198,7 +198,12 @@ assert_gate_port_is_free() {
|
||||
local proto_flag="$2"
|
||||
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
if ss -H "$proto_flag" 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${port}$"; then
|
||||
# Вывод собирается ДО поиска: `grep -q` обрывает чтение на первом совпадении,
|
||||
# и под `pipefail` SIGPIPE у продюсера превратил бы найденный занятый порт в
|
||||
# ненулевой статус, то есть в вывод «порт свободен».
|
||||
local sockets
|
||||
sockets="$(ss -H "$proto_flag" 2>/dev/null | awk '{print $4}')" || sockets=""
|
||||
if grep -Eq "[:.]${port}$" <<<"$sockets"; then
|
||||
fail "compatibility gate port $port is already in use; override HYSTERIA_GATE_PORT/HYSTERIA_GATE_STATS_PORT"
|
||||
fi
|
||||
fi
|
||||
@@ -246,10 +251,13 @@ gate_run_single_profile() {
|
||||
if ! kill -0 "$gate_pid" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
if command -v ss >/dev/null 2>&1 \
|
||||
&& ss -H -lun 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${HYSTERIA_GATE_PORT}$"; then
|
||||
ready="true"
|
||||
break
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
local udp_sockets
|
||||
udp_sockets="$(ss -H -lun 2>/dev/null | awk '{print $4}')" || udp_sockets=""
|
||||
if grep -Eq "[:.]${HYSTERIA_GATE_PORT}$" <<<"$udp_sockets"; then
|
||||
ready="true"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
|
||||
Reference in New Issue
Block a user