Skip to content

fix: block direct Future construction and report the released version… #214

fix: block direct Future construction and report the released version…

fix: block direct Future construction and report the released version… #214

Workflow file for this run

name: "Test PHP Extension"
on:
push:
branches:
- trunk
- v1.x
- v2.x
paths:
- 'cmake/**'
- 'scripts/**'
- 'src/**'
- 'include/**'
- 'packages/**'
- 'util/**'
- 'ZendCPP/**'
- 'tests/**'
- 'CMakeLists.txt'
- 'composer.json'
- '.github/workflows/test.yml'
- '.github/actions/**'
pull_request:
branches:
- trunk
- v1.x
- v2.x
paths:
- 'cmake/**'
- 'scripts/**'
- 'src/**'
- 'include/**'
- 'packages/**'
- 'util/**'
- 'ZendCPP/**'
- 'tests/**'
- 'CMakeLists.txt'
- 'composer.json'
- '.github/workflows/test.yml'
- '.github/actions/**'
workflow_dispatch:
permissions:
contents: read
jobs:
lint-zpp:
name: "Lint: no legacy zend_parse_parameters"
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Reject zend_parse_parameters() (use ZEND_PARSE_PARAMETERS_START/END)
run: |
set -e
hits=$(grep -rEn 'zend_parse_parameters\b' src/ --include='*.cpp' --include='*.c' \
| grep -v 'zend_parse_parameters_none\|zend_parse_parameters_throw' || true)
if [ -n "$hits" ]; then
echo "Legacy zend_parse_parameters() callsites found. Use ZEND_PARSE_PARAMETERS_START / Z_PARAM_* macros instead:"
echo "$hits"
exit 1
fi
echo "OK — no legacy zend_parse_parameters() callsites."
lint-clang-tidy:
name: "Lint: clang-tidy (DebugPHP8.5NTS)"
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential ninja-build libuv1-dev \
clang-tidy-19
sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-19 100
clang-tidy --version
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.5"
ini-file: development
coverage: none
tools: php-config, phpize
extensions: mbstring, curl, intl
env:
phpts: nts
debug: true
- name: Setup CMake
uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2
with:
cmake-version: "3.30"
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: clang-tidy-${{ runner.os }}-8.5-nts
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: scylladb
- name: Configure (DebugPHP8.5NTS, sanitizers off, export compile commands)
run: |
export PKG_CONFIG_PATH="${{ steps.driver.outputs.pkg_config_path }}:$(pkg-config --variable pc_path pkg-config 2>/dev/null || true)"
cmake --preset DebugPHP8.5NTS \
-DCUSTOM_PHP_CONFIG="$(command -v php-config)" \
-DENABLE_SANITIZERS=OFF \
-DSANITIZE_ADDRESS=OFF \
-DSANITIZE_UNDEFINED=OFF \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Build generated headers
# clang-tidy needs *_arginfo.h and *_descriptor.c to exist before it
# can parse a translation unit. The generated-sources target collects
# every generated file from cmake/GenStubs.cmake, so a new module is
# covered without an edit here.
run: |
cmake --build out/DebugPHP8.5NTS --target ZendCPP || true
cmake --build out/DebugPHP8.5NTS --target generated-sources
- name: Determine files to lint
id: files
run: |
set -e
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
git diff --name-only --diff-filter=AM "$BASE" "$HEAD" \
| grep -E '^src/.*\.(c|cc|cpp)$' > changed.txt || true
else
find src -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' \) > changed.txt
fi
# Sources this PHP 8.5 lint build cannot parse:
# SwooleBridge.cc needs the swoole build flag + (open)swoole headers.
# Poll.c / PollHandle.c need main/php_poll.h, which only PHP >= 8.6 ships.
grep -vE 'src/Async/(SwooleBridge\.cc|Poll\.c|PollHandle\.c)$' changed.txt > changed.tmp || true
mv changed.tmp changed.txt
echo "count=$(wc -l < changed.txt | tr -d ' ')" >> "$GITHUB_OUTPUT"
echo "Files to lint:"
cat changed.txt || true
- name: Run clang-tidy
if: steps.files.outputs.count != '0'
run: |
set -e
xargs -a changed.txt -r -n1 -P"$(nproc)" \
clang-tidy -p out/DebugPHP8.5NTS --quiet
test:
name: PHP ${{ matrix.php }}-${{ matrix.phpts }} / ${{ matrix.driver }}
runs-on: ubuntu-26.04
# scylla-rust is experimental — don't fail the whole CI when it breaks
continue-on-error: ${{ matrix.driver == 'scylla-rust' }}
services:
scylladb:
image: scylladb/scylla:2026.1
options: >-
--health-cmd "cqlsh -e 'SELECT cluster_name FROM system.local' 2>/dev/null"
--health-interval 10s
--health-timeout 5s
--health-retries 30
--health-start-period 60s
ports:
- 9042:9042
strategy:
fail-fast: false
matrix:
php:
- "8.3"
- "8.4"
- "8.5"
phpts:
- "nts"
- "ts"
driver:
- "scylladb"
- "cassandra"
- "scylla-rust"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential ninja-build libuv1-dev
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
ini-file: development
coverage: none
tools: pie, composer, php-config, phpize, pecl
extensions: pcntl, mbstring, curl, intl
env:
phpts: ${{ matrix.phpts }}
debug: true
fail-fast: true
- name: Setup CMake
uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2
with:
cmake-version: "3.30"
# ccache must be set up before any cmake build step so all
# C/C++ compilation (cpp-driver, extension) benefits from it.
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.phpts }}-${{ matrix.driver }}
restore-keys: |
${{ runner.os }}-${{ matrix.php }}-${{ matrix.phpts }}-${{ matrix.driver }}
${{ runner.os }}-${{ matrix.php }}-${{ matrix.phpts }}-
${{ runner.os }}-${{ matrix.php }}-
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: ${{ matrix.driver }}
- uses: ./.github/actions/build-extension
with:
driver: ${{ matrix.driver }}
cpp_driver_pkg_config_path: ${{ steps.driver.outputs.pkg_config_path }}
- name: Cache Composer dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: vendor
key: composer-${{ matrix.php }}-${{ hashFiles('composer.json', 'composer.lock') }}
restore-keys: composer-${{ matrix.php }}-
- name: Install Composer dependencies
run: |
php -d extension=cassandra "$(command -v composer)" \
install --no-ansi --no-interaction --no-progress --prefer-dist
- name: Run tests
run: |
php -d extension=cassandra ./vendor/bin/pest --colors=always
env:
SCYLLADB_HOSTS: "127.0.0.1"
SCYLLADB_PHP_BACKEND: ${{ steps.driver.outputs.backend }}
test-asan:
name: "ASan: PHP ${{ matrix.php }}-nts / scylladb (memory-safety guard)"
runs-on: ubuntu-26.04
services:
scylladb:
image: scylladb/scylla:2026.1
options: >-
--health-cmd "cqlsh -e 'SELECT cluster_name FROM system.local' 2>/dev/null"
--health-interval 10s
--health-timeout 5s
--health-retries 30
--health-start-period 60s
ports:
- 9042:9042
# 8.5 only, deliberately. This job LD_PRELOADs the ASan runtime into a
# stock (non-instrumented) setup-php binary, and PHP <= 8.4 dlopen()s
# shared extensions with RTLD_DEEPBIND, which the sanitizer runtime
# refuses to work with (google/sanitizers#611). With abort_on_error=1
# that is a SIGABRT during startup — on opcache.so, then mysqlnd.so, and
# ultimately on cassandra.so itself — so the 8.4 leg aborted before it
# could run a single test and had been permanently red while guarding
# nothing. It is not fixable by disabling individual extensions: the
# extension under test is itself a shared object. 8.5 does not pass
# RTLD_DEEPBIND and runs the full suite clean.
strategy:
fail-fast: false
matrix:
php:
- "8.5"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential ninja-build libuv1-dev
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
ini-file: development
coverage: none
tools: composer, php-config, phpize
extensions: pcntl, mbstring, curl, intl
env:
phpts: nts
debug: true
fail-fast: true
- name: Setup CMake
uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2
with:
cmake-version: "3.30"
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: asan-${{ runner.os }}-${{ matrix.php }}-nts-scylladb
restore-keys: |
asan-${{ runner.os }}-${{ matrix.php }}-nts-scylladb
asan-${{ runner.os }}-${{ matrix.php }}-
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: scylladb
# AddressSanitizer-instrumented build. This is the regression guard for
# the cycle-collector use-after-free (a shared type's CassDataType freed
# by a mis-accounted get_gc): the whole suite was clean under ASan only
# after the get_gc handlers were fixed. A plain test run is not a reliable
# guard — the fault is heap-layout dependent and need not crash on x86.
- uses: ./.github/actions/build-extension
with:
driver: scylladb
cpp_driver_pkg_config_path: ${{ steps.driver.outputs.pkg_config_path }}
sanitize_address: 'ON'
- name: Install Composer dependencies
run: |
composer install --no-ansi --no-interaction --no-progress --prefer-dist
- name: Run tests under AddressSanitizer
# The extension is ASan-instrumented but the setup-php binary is not, so
# the ASan runtime is LD_PRELOADed and USE_ZEND_ALLOC=0 routes Zend
# allocations through it. detect_leaks=0 because PHP intentionally leaks
# persistent allocations at shutdown; detect_odr_violation=0 avoids false
# positives from the statically bundled C++ driver. halt_on_error=1 fails
# the job on the first heap-use-after-free / overflow.
run: |
ASAN_RT="$(gcc -print-file-name=libasan.so)"
echo "ASan runtime: ${ASAN_RT}"
LD_PRELOAD="${ASAN_RT}" \
USE_ZEND_ALLOC=0 \
ASAN_OPTIONS="detect_leaks=0:detect_odr_violation=0:verify_asan_link_order=0:abort_on_error=1:halt_on_error=1" \
php -d extension=cassandra ./vendor/bin/pest --colors=always
env:
SCYLLADB_HOSTS: "127.0.0.1"
SCYLLADB_PHP_BACKEND: ${{ steps.driver.outputs.backend }}
poll:
name: "Io\\Poll: PHP 8.6 / scylladb"
runs-on: ubuntu-24.04
# PHP 8.6 is unreleased and the polling API is still moving (Context::wait()
# changed shape after 8.6.0alpha3), so the job cannot build reliably yet.
# Run it on demand only. Remove this gate when PHP 8.6 is stable.
if: ${{ github.event_name == 'workflow_dispatch' }}
continue-on-error: true
services:
scylladb:
image: scylladb/scylla:2026.1
options: >-
--health-cmd "cqlsh -e 'SELECT cluster_name FROM system.local' 2>/dev/null"
--health-interval 10s
--health-timeout 5s
--health-retries 30
--health-start-period 60s
ports:
- 9042:9042
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential ninja-build libuv1-dev
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.6"
ini-file: development
coverage: none
tools: composer, php-config, phpize
extensions: pcntl, mbstring, curl, intl
- name: Verify this PHP ships the polling API
run: |
php -r 'exit(class_exists("Io\\Poll\\Context") ? 0 : 1);' \
|| { echo "::error::PHP $(php -r 'echo PHP_VERSION;') has no Io\Poll — nothing to test"; exit 1; }
test -f "$(php-config --include-dir)/main/php_poll.h" \
|| { echo "::error::main/php_poll.h is missing from this PHP's headers"; exit 1; }
- name: Setup CMake
uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2
with:
cmake-version: "3.30"
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: poll-${{ runner.os }}-8.6
restore-keys: poll-${{ runner.os }}-
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: scylladb
# ON, not AUTO: a PHP without the header must fail the build loudly rather
# than quietly produce an extension with no Poll classes and tests that
# skip themselves green.
- uses: ./.github/actions/build-extension
with:
driver: scylladb
cpp_driver_pkg_config_path: ${{ steps.driver.outputs.pkg_config_path }}
enable_poll_api: "ON"
- name: Verify the Poll classes are present
run: |
php -d extension=cassandra -r '
exit(class_exists("Cassandra\\Async\\Poll")
&& class_exists("Cassandra\\Async\\PollHandle") ? 0 : 1);' \
|| { echo "::error::extension built without Cassandra\Async\Poll"; exit 1; }
- name: Install Composer dependencies
run: |
php -d extension=cassandra "$(command -v composer)" \
install --no-ansi --no-interaction --no-progress --prefer-dist --ignore-platform-req=php
- name: Run tests
run: |
php -d extension=cassandra ./vendor/bin/pest --colors=always
env:
SCYLLADB_HOSTS: "127.0.0.1"
SCYLLADB_PHP_BACKEND: ${{ steps.driver.outputs.backend }}
swoole:
name: "Swoole: PHP ${{ matrix.php }} / ${{ matrix.flavor }}"
runs-on: ubuntu-24.04
services:
scylladb:
image: scylladb/scylla:2026.1
options: >-
--health-cmd "cqlsh -e 'SELECT cluster_name FROM system.local' 2>/dev/null"
--health-interval 10s
--health-timeout 5s
--health-retries 30
--health-start-period 60s
ports:
- 9042:9042
strategy:
fail-fast: false
matrix:
# Native (Open)Swoole coroutine support built with the opt-in flag and
# exercised against the matching runtime. openswoole lags PHP support, so
# it is pinned to 8.3. The C++ shim is compiled against the runtime's own
# source headers (checked out at the installed version for ABI match).
include:
- php: "8.3"
flavor: swoole
repo: swoole/swoole-src
- php: "8.4"
flavor: swoole
repo: swoole/swoole-src
- php: "8.3"
flavor: openswoole
repo: openswoole/swoole-src
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential ninja-build libuv1-dev libssl-dev
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
ini-file: development
coverage: none
tools: composer, php-config, phpize
extensions: pcntl, mbstring, curl, intl, ${{ matrix.flavor }}
env:
phpts: nts
debug: true
- name: Setup CMake
uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2
with:
cmake-version: "3.30"
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: swoole-${{ runner.os }}-${{ matrix.php }}-${{ matrix.flavor }}
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: scylladb
- name: Fetch ${{ matrix.flavor }} source headers (ABI-matched)
run: |
set -e
VER="$(php -r "echo phpversion('${{ matrix.flavor }}');")"
echo "Installed ${{ matrix.flavor }} version: ${VER}"
git clone --depth 1 --branch "v${VER}" "https://github.com/${{ matrix.repo }}.git" /tmp/swoole-src \
|| git clone --depth 1 --branch "${VER}" "https://github.com/${{ matrix.repo }}.git" /tmp/swoole-src
- name: Build extension (native ${{ matrix.flavor }})
run: |
set -e
export PKG_CONFIG_PATH="${{ steps.driver.outputs.pkg_config_path }}:$(pkg-config --variable pc_path pkg-config 2>/dev/null || true)"
if [ "${{ matrix.flavor }}" = "openswoole" ]; then
SWOOLE_FLAG="-DPHP_SCYLLADB_ENABLE_OPENSWOOLE=ON"
else
SWOOLE_FLAG="-DPHP_SCYLLADB_ENABLE_SWOOLE=ON"
fi
cmake -G Ninja -B cmake-build \
-DCUSTOM_PHP_CONFIG="$(command -v php-config)" \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DPHP_SCYLLADB_STATIC=ON \
-DPHP_SCYLLADB_BACKEND=scylla-cpp \
${SWOOLE_FLAG} \
-DPHP_SCYLLADB_SWOOLE_SRC=/tmp/swoole-src \
-DCMAKE_CXX_FLAGS="$(pkg-config --cflags-only-I openssl 2>/dev/null || true)"
cmake --build cmake-build --parallel "$(nproc)"
sudo cmake --install cmake-build --component extension
- name: Install Composer dependencies
run: |
php -d extension=cassandra "$(command -v composer)" \
install --no-ansi --no-interaction --no-progress --prefer-dist
- name: Run Swoole tests
# setup-php already loads the coroutine runtime via php.ini (so it is
# available to the native shim); only cassandra needs adding here.
run: |
php -d extension=cassandra \
./vendor/bin/pest --group=swoole --colors=always
env:
SCYLLADB_HOSTS: "127.0.0.1"
pie-build:
name: PIE / PHP ${{ matrix.php }}-nts / ${{ matrix.driver }}
runs-on: ubuntu-26.04
strategy:
fail-fast: false
matrix:
php:
- "8.3"
- "8.4"
- "8.5"
driver:
- "scylladb"
- "cassandra"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
submodules: recursive
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
pkg-config build-essential libssl-dev libuv1-dev
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
ini-file: development
coverage: none
tools: pie, php-config, phpize
extensions: mbstring, curl, intl
env:
debug: true
- name: Setup ccache
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2
with:
key: pie-${{ runner.os }}-${{ matrix.php }}-${{ matrix.driver }}
restore-keys: |
pie-${{ runner.os }}-${{ matrix.php }}-${{ matrix.driver }}
pie-${{ runner.os }}-${{ matrix.php }}-
max-size: 500M
append-timestamp: false
- uses: ./.github/actions/install-cpp-driver
id: driver
with:
driver: ${{ matrix.driver }}
- name: Build with PIE
run: |
DRIVER_OPT=""
[[ "${{ matrix.driver }}" == "cassandra" ]] && DRIVER_OPT="--enable-libcassandra"
export PKG_CONFIG_PATH="${{ steps.driver.outputs.pkg_config_path }}:$(pkg-config --variable pc_path pkg-config 2>/dev/null || true)"
pie build codelieutenant/scylla-driver \
${DRIVER_OPT} \
--with-php-config="$(command -v php-config)" \
--no-interaction \
--no-ansi
env:
CC: ccache gcc
CXX: ccache g++
# The PIE path does not go through CMake, so the same gh-117 guard is
# invoked directly here: every symbol the module references must come
# from a library it declares, not from whichever extension happened to
# load first. The isolated load (-n, no php.ini) is a second, weaker
# check — see scripts/check-module-symbols.sh for why it is not enough.
- name: Verify extension loads (isolated, eager binding)
run: |
EXT_SO=$(find ~/.config/pie -name "cassandra.so" | head -1)
echo "Built: ${EXT_SO}"
test -f "${EXT_SO}"
sh scripts/check-module-symbols.sh "${EXT_SO}" "$(command -v php)"
LD_BIND_NOW=1 php -n -d extension="${EXT_SO}" -m | grep -i cassandra