Skip to content

PHP-WASM mysqli_poll does not honor its timeout for a pending async MySQL query #4161

Description

@chubes4

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions