Skip to content

Commit 67fd643

Browse files
committed
ci: add workflow_dispatch bisect for Symfony Console JIT hang
The nightly COMMUNITY_asan job hangs every run in the Symfony Console OutputFormatter tests (test php#672, testInlineStyle) under tracing JIT, exhausting GitHub's 6h ceiling. The hang is an x86-64 tracing-JIT regression present in 8.4/8.5/8.6 but not 8.3, and does not reproduce on ARM or under qemu emulation. Add a manually-triggered bisect that runs on a native x86-64 runner, building ZTS+JIT+ASAN (matching COMMUNITY_asan) and using a Console-suite hang as the bisect signal, to pinpoint the introducing commit.
1 parent 27c10dd commit 67fd643

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: JIT Hang Bisect
2+
# Manually-triggered bisect for the Symfony Console OutputFormatter tracing-JIT
3+
# hang seen in the nightly COMMUNITY_asan job. Runs on a native x86-64 runner
4+
# (the bug does not reproduce on ARM or under qemu emulation).
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
good:
9+
description: 'Known-GOOD commit/tag (bug absent)'
10+
default: 'php-8.3.0'
11+
bad:
12+
description: 'Known-BAD commit/tag (hang)'
13+
default: '27c10ddce6302912a46d72a4dcd47c89f1209544'
14+
symfony_commit:
15+
description: 'Symfony commit to test against (CI nightly pin)'
16+
default: 'dce1b899ab0c0e4cef9159c083ef535387a8db12'
17+
test_timeout:
18+
description: 'Per-step test timeout in seconds (hang detector)'
19+
default: '300'
20+
use_asan:
21+
description: 'Build with ASAN+UBSAN (matches COMMUNITY_asan exactly)'
22+
default: 'true'
23+
24+
permissions:
25+
contents: read
26+
27+
jobs:
28+
bisect:
29+
runs-on: ubuntu-24.04
30+
timeout-minutes: 350
31+
env:
32+
SYMFONY_DIR: ${{ runner.temp }}/symfony
33+
TEST_TIMEOUT: ${{ inputs.test_timeout }}
34+
USE_ASAN: ${{ inputs.use_asan }}
35+
# Mirror the COMMUNITY_asan runtime environment
36+
USE_ZEND_ALLOC: '0'
37+
USE_TRACKED_ALLOC: '1'
38+
ASAN_OPTIONS: 'exitcode=139'
39+
UBSAN_OPTIONS: 'print_stacktrace=1'
40+
SYMFONY_DEPRECATIONS_HELPER: 'max[total]=999'
41+
steps:
42+
- name: Checkout php-src (full history)
43+
uses: actions/checkout@v6
44+
with:
45+
fetch-depth: 0
46+
47+
- name: Ensure bisect endpoints & tags are present
48+
run: |
49+
git remote add upstream https://github.com/php/php-src.git || true
50+
git fetch --tags --quiet upstream
51+
git rev-parse "${{ inputs.good }}^{commit}"
52+
git rev-parse "${{ inputs.bad }}^{commit}"
53+
54+
- name: Install build dependencies
55+
run: |
56+
sudo apt-get update
57+
sudo apt-get install -y --no-install-recommends \
58+
build-essential autoconf automake libtool bison re2c pkg-config ccache \
59+
libxml2-dev libsqlite3-dev libonig-dev libssl-dev zlib1g-dev \
60+
php-cli php-xml php-mbstring composer
61+
62+
- name: Set up Symfony test bed (pinned commit)
63+
run: |
64+
git clone --no-checkout --filter=blob:none https://github.com/symfony/symfony.git "$SYMFONY_DIR"
65+
git -C "$SYMFONY_DIR" fetch --depth=1 origin "${{ inputs.symfony_commit }}"
66+
git -C "$SYMFONY_DIR" checkout "${{ inputs.symfony_commit }}"
67+
( cd "$SYMFONY_DIR" && composer install --no-progress --ignore-platform-req=php+ && php ./phpunit install )
68+
69+
- name: Write bisect predicate
70+
run: |
71+
cat > "${{ runner.temp }}/bisect_step.sh" <<'STEP'
72+
#!/usr/bin/env bash
73+
# exit 0=GOOD, 1=BAD(hang), 125=SKIP(untestable)
74+
set -u
75+
NCPU="$(nproc)"
76+
WS="$PWD"
77+
rev="$(git rev-parse --short HEAD)"
78+
export CC="ccache gcc" CXX="ccache g++"
79+
80+
make distclean >/dev/null 2>&1
81+
./buildconf --force >/tmp/bc.log 2>&1 || { echo "[$rev] buildconf fail -> skip"; exit 125; }
82+
83+
CONF=( --enable-debug --enable-zts --enable-opcache
84+
--enable-mbstring --enable-tokenizer
85+
--with-libxml --enable-dom --enable-xml --enable-xmlwriter --enable-xmlreader --enable-simplexml
86+
--enable-ctype --enable-filter --enable-fileinfo --enable-phar --enable-posix --enable-pcntl
87+
--disable-cgi )
88+
if [ "${USE_ASAN:-true}" = "true" ]; then
89+
CONF+=( "CFLAGS=-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC"
90+
"LDFLAGS=-fsanitize=undefined,address" )
91+
fi
92+
./configure "${CONF[@]}" >/tmp/conf.log 2>&1 || { echo "[$rev] configure fail -> skip"; tail -5 /tmp/conf.log; exit 125; }
93+
make -j"$NCPU" >/tmp/make.log 2>&1 || { echo "[$rev] make fail -> skip"; tail -15 /tmp/make.log; exit 125; }
94+
95+
PHP_BIN="$WS/sapi/cli/php"
96+
OC="$(find "$WS" -name opcache.so -path '*/modules/*' | head -1)"
97+
[ -x "$PHP_BIN" ] && [ -n "$OC" ] || { echo "[$rev] no binary -> skip"; exit 125; }
98+
JIT=( -dzend_extension="$OC" -dopcache.enable_cli=1 -dopcache.jit=tracing -dopcache.jit_buffer_size=1G
99+
-dopcache.jit_hot_loop=1 -dopcache.jit_hot_func=1 -dopcache.jit_hot_return=1
100+
-dopcache.jit_hot_side_exit=1 -dmemory_limit=-1 )
101+
"$PHP_BIN" "${JIT[@]}" -r 'exit((opcache_get_status(false)["jit"]["enabled"]??false)?0:3);' \
102+
|| { echo "[$rev] JIT not enabled -> skip"; exit 125; }
103+
104+
cd "$SYMFONY_DIR" || { echo "[$rev] no symfony -> skip"; exit 125; }
105+
echo "[$rev] running Console suite (timeout ${TEST_TIMEOUT}s)..."
106+
timeout "${TEST_TIMEOUT}" "$PHP_BIN" "${JIT[@]}" ./phpunit src/Symfony/Component/Console \
107+
--exclude-group tty --exclude-group benchmark --exclude-group intl-data \
108+
--exclude-group transient --exclude-group skip >/tmp/test.log 2>&1
109+
rc=$?
110+
case "$rc" in
111+
124) echo "[$rev] HANG (timeout) -> BAD"; exit 1 ;;
112+
0|1|2) echo "[$rev] completed rc=$rc -> GOOD"; exit 0 ;;
113+
*) echo "[$rev] abnormal rc=$rc (asan abort / crash) -> SKIP"; tail -5 /tmp/test.log; exit 125 ;;
114+
esac
115+
STEP
116+
chmod +x "${{ runner.temp }}/bisect_step.sh"
117+
118+
- name: Run git bisect
119+
run: |
120+
git bisect reset >/dev/null 2>&1 || true
121+
git bisect start
122+
git bisect bad "${{ inputs.bad }}"
123+
git bisect good "${{ inputs.good }}"
124+
set +e
125+
git bisect run "${{ runner.temp }}/bisect_step.sh" | tee "${{ runner.temp }}/bisect_out.txt"
126+
set -e
127+
128+
- name: Report first-bad commit
129+
if: always()
130+
run: |
131+
{
132+
echo "## JIT hang bisect result"
133+
echo '```'
134+
grep -E "is the first bad commit" -A0 "${{ runner.temp }}/bisect_out.txt" || echo "(no clear first-bad — see log below)"
135+
echo '```'
136+
echo "### Full bisect log"
137+
echo '```'
138+
git bisect log 2>/dev/null || true
139+
echo '```'
140+
} >> "$GITHUB_STEP_SUMMARY"
141+
git bisect reset >/dev/null 2>&1 || true

0 commit comments

Comments
 (0)