Summary
In PHP-WASM, mysqli_poll($read, $error, $reject, 0, 100000) does not return after its requested 100ms timeout when an asynchronous MySQL query is waiting on an InnoDB row lock. The PHP execution remains pending until an external recipe timeout aborts it.
This is distinct from #4160: the worker rejection is secondary. This issue covers the initiating mysqli_poll stall itself.
Bounded reproduction
Environment:
@wp-playground/cli@3.1.46
- Node.js 22 on
ubuntu-latest
- reachable disposable MySQL service over TCP
- WP Codebox commit
860bff1182f09365b626297e6dd5d4583fe53e92
- outer recipe timeout: 20 seconds
Minimal PHP behavior:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$first = mysqli_init();
$second = mysqli_init();
mysqli_real_connect($first, getenv("DB_HOST"), "root", "", "runtime", (int) getenv("DB_PORT"));
mysqli_real_connect($second, getenv("DB_HOST"), "root", "", "runtime", (int) getenv("DB_PORT"));
$first->query("CREATE TABLE poll_lock (id INT PRIMARY KEY) ENGINE=InnoDB");
$first->query("INSERT INTO poll_lock VALUES (1)");
$first->query("START TRANSACTION");
$first->query("SELECT id FROM poll_lock WHERE id = 1 FOR UPDATE");
$second->query("START TRANSACTION");
$second->query("SELECT id FROM poll_lock WHERE id = 1 FOR UPDATE", MYSQLI_ASYNC);
$read = [$second];
$error = [];
$reject = [];
$started = microtime(true);
$ready = mysqli_poll($read, $error, $reject, 0, 100000);
echo json_encode([
"ready" => $ready,
"elapsed_ms" => round((microtime(true) - $started) * 1000),
]);
Full executable regression commit:
Automattic/wp-codebox@860bff1
Linux/Docker evidence run:
https://github.com/Automattic/wp-codebox/actions/runs/30120500876/job/89571652650
Expected
mysqli_poll() returns 0 after approximately 100ms because the asynchronous query is still waiting on the row lock. PHP then releases the lock and completes normally.
Actual
The PHP operation never returns from mysqli_poll(). The outer runner records:
Recipe run timed out after 20001ms while waiting for workflow.steps[0]:wordpress.run-php
During abort/cleanup, the worker also emits:
Unhandled rejection: RuntimeError: null function or function signature mismatch
at php.wasm._php_stream_write_filtered
at php.wasm._php_stream_flush
at php.wasm._php_stream_cast
at php.wasm.mysqlnd_stream_array_from_fd_set
at php.wasm.zif_mysqli_poll
The same operation originally appeared in a full PHPUnit run with a 25-minute outer bound. A corrected chronology on #4160 established that the rejection there happened after timeout cleanup; this bounded reproduction removes PHPUnit discovery and reproduces the initiating operation directly.
Source observations
Current Playground source replaces PHP polling with wasm_poll_socket(), which loops in 10ms intervals until a poll mask or interruption:
https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/php_wasm.c#L138-L309
php_pollfd_for() delegates into that bridge:
https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/php_wasm.c#L824-L848
The current ASYNCIFY_ONLY list includes many mysqlnd functions and php_pollfd_for, but does not include mysqlnd_stream_array_from_fd_set or zif_mysqli_poll:
https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/Dockerfile#L1124-L1193
That omission matches the cleanup stack, but the precise cause of the timeout still needs upstream confirmation.
Related
AI assistance
OpenAI GPT-5.6 Sol via OpenCode traced the original workload, prepared the bounded reproduction, searched related issues/source, and assembled the evidence. Chris Huber reviewed and remains responsible for the report.
Summary
In PHP-WASM,
mysqli_poll($read, $error, $reject, 0, 100000)does not return after its requested 100ms timeout when an asynchronous MySQL query is waiting on an InnoDB row lock. The PHP execution remains pending until an external recipe timeout aborts it.This is distinct from #4160: the worker rejection is secondary. This issue covers the initiating
mysqli_pollstall itself.Bounded reproduction
Environment:
@wp-playground/cli@3.1.46ubuntu-latest860bff1182f09365b626297e6dd5d4583fe53e92Minimal PHP behavior:
Full executable regression commit:
Automattic/wp-codebox@860bff1
Linux/Docker evidence run:
https://github.com/Automattic/wp-codebox/actions/runs/30120500876/job/89571652650
Expected
mysqli_poll()returns0after approximately 100ms because the asynchronous query is still waiting on the row lock. PHP then releases the lock and completes normally.Actual
The PHP operation never returns from
mysqli_poll(). The outer runner records:During abort/cleanup, the worker also emits:
The same operation originally appeared in a full PHPUnit run with a 25-minute outer bound. A corrected chronology on #4160 established that the rejection there happened after timeout cleanup; this bounded reproduction removes PHPUnit discovery and reproduces the initiating operation directly.
Source observations
Current Playground source replaces PHP polling with
wasm_poll_socket(), which loops in 10ms intervals until a poll mask or interruption:https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/php_wasm.c#L138-L309
php_pollfd_for()delegates into that bridge:https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/php_wasm.c#L824-L848
The current
ASYNCIFY_ONLYlist includes many mysqlnd functions andphp_pollfd_for, but does not includemysqlnd_stream_array_from_fd_setorzif_mysqli_poll:https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/compile/php/Dockerfile#L1124-L1193
That omission matches the cleanup stack, but the precise cause of the timeout still needs upstream confirmation.
Related
AI assistance
OpenAI GPT-5.6 Sol via OpenCode traced the original workload, prepared the bounded reproduction, searched related issues/source, and assembled the evidence. Chris Huber reviewed and remains responsible for the report.