Skip to content

Throwaway PR: Share hacked setup for debugging fcntl() Asyncify errors. #2242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: add-fcntl-for-nodejs
Choose a base branch
from

Conversation

brandonpayton
Copy link
Member

@adamziel, here is my current debug setup.

To test, I am effectively doing the following:

  • Creating a directory for a WP install and populate it using Playground CLI
  • Copy the many-writes folder from this project root to its wp-content dir
  • nvm install 23
  • npm ci
  • npx nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.3 --WITH_DEBUG=yes
  • Open the project in Cursor
  • Set a breakpoint in packages/php-wasm/node/asyncify/php_8_3.js at the start of the __syscall_fcntl64() function.
  • Use Cursor to run the "Debug Playground CLI" launch target
    • Note: You will need to adjust the path to the blueprint here. I just committed a test-fcntl-blueprint.json to the project root, so you may be able to make that a relative reference.
  • When you hit the breakpoint, enable catching caught/uncaught exceptions
  • Continue execution and observe the error

@brandonpayton
Copy link
Member Author

There may be something I forgot to mention, but please feel free to DM me if you have any issues.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

How would we know if this was a wrong way of adding an async call in the sys fcntl function? Note how our async functions are shipped via the C file and wrapped in the ES macros - different for jspi and asyncify. Any chance we're not telling Emscripten to expect stack switching on this entire fcntl code path? Would we have to patch musl?

@brandonpayton
Copy link
Member Author

brandonpayton commented Jun 7, 2025

@adamziel, I believe what we are doing follows the same pattern as the JS Emscripten generates with the EM_ASYNC_JS() macro.

@brandonpayton
Copy link
Member Author

brandonpayton commented Jun 7, 2025

If you look at one of those macro-wrapped functions in the generated JS, I believe they are async functions wrapped with Asyncify.handleAsync().

I might be missing something though. Is that what you see when you look at the generated JS?

@brandonpayton
Copy link
Member Author

I am mobile but will double check in a bit.

@brandonpayton
Copy link
Member Author

Note how our async functions are shipped via the C file and wrapped in the ES macros - different for jspi and asyncify.

OK, I see how we are often wrapping them differently between EM_JS and EM_ASYNC_JS:

#ifdef PLAYGROUND_JSPI
EM_ASYNC_JS(char*, js_popen_to_file, (const char *command, const char *mode, uint8_t *exitCodePtr), {
const returnCallback = (resolver) => new Promise(resolver);
#else
EM_JS(char*, js_popen_to_file, (const char *command, const char *mode, uint8_t *exitCodePtr), {
const returnCallback = (resolver) => Asyncify.handleSleep(resolver);
#endif

There is one function that we always wrap with EM_ASYNC_JS:

*/
EM_ASYNC_JS(size_t, js_module_onMessage, (const char *data, char **response_buffer), {
if (Module['onMessage']) {

The docs for the macro imply that it can be used for both Asyncify and JSPI builds:
https://emscripten.org/docs/porting/asyncify.html#differences-between-asyncify-and-jspi

_Note:
<JSPI/ASYNCIFY>IMPORTS and JSPI_EXPORTS aren’t needed when using various helpers mentioned above such as: EM_ASYNC_JS, Embind’s Async support, ccall, etc…

The EM_ASYNC_JS macro looks like this:

#define EM_ASYNC_JS(ret, name, params, ...) _EM_JS(ret, name, __asyncjs__##name, params,          \
  "{ return Asyncify.handleAsync(async () => " #__VA_ARGS__ "); }")

And it does three things:

  1. Names a function with an __asyncjs__ prefix
  2. Wraps the code in Asyncify.handleAsync()
  3. Wraps the code in an async function passed to handleAsync().

In this PR, __syscall_fcntl64() wraps its code as an async function passed to Asyncify.handleAsync(), but it doesn't use the asyncjs prefix. I think this omission is OK though. AFAICT, the prefix is only used to auto-add the function to ASYNCIFY_IMPORTS:

DEFAULT_ASYNCIFY_IMPORTS = ['__asyncjs__*']

@brandonpayton
Copy link
Member Author

There is some other Emscripten code that auto-adds JS functions to ASYNCIFY_IMPORTS:

From jsifier.mjs, this code identifies async library functions:

          isAsyncFunction =
            LibraryManager.library[symbol + '__async'] ||
            original.constructor.name == 'AsyncFunction';

and adds those functions to an asyncFuncs list here:

        if (isAsyncFunction) {
          asyncFuncs.push(symbol);
        }

and relays the list in this data structure.

Then that list appears to be added to ASYNCIFY_IMPORTS here:

settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']]

It's no big deal either way, but I think our current declaration of __syscall_fcntl64() could be auto-added to the list because it is an async function:

	__syscall_fcntl64: async function __syscall_fcntl64(fd, cmd, varargs) {

@brandonpayton
Copy link
Member Author

So, it's possible I'm wrong, but I think that the way __syscall_fcntl64() is defined should be fine based on these things. Also, after testing an Asyncify build without ASYNCIFY_ONLY, the override appears to work.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

Good investigation, thank you @brandonpayton! I've played with adding all SQLite3 exports to asyncify list but no bueno yet. There may be some private, unexported functions that we need to add, and some additional core PHP functions

"sqlite3_status64",\
"sqlite3_mutex_enter",\
"sqlite3_mutex_leave",\
"sqlite3_status",\
"sqlite3_db_status",\
"sqlite3_msize",\
"sqlite3_vfs_find",\
"sqlite3_initialize",\
"sqlite3_mutex_free",\
"sqlite3_vfs_register",\
"sqlite3_vfs_unregister",\
"sqlite3_mutex_alloc",\
"sqlite3_mutex_try",\
"sqlite3_release_memory",\
"sqlite3_memory_alarm",\
"sqlite3_soft_heap_limit64",\
"sqlite3_memory_used",\
"sqlite3_soft_heap_limit",\
"sqlite3_hard_heap_limit64",\
"sqlite3_memory_highwater",\
"sqlite3_malloc",\
"sqlite3_malloc64",\
"sqlite3_free",\
"sqlite3_realloc",\
"sqlite3_realloc64",\
"sqlite3_str_vappendf",\
"sqlite3_str_append",\
"sqlite3_str_appendchar",\
"sqlite3_str_appendall",\
"sqlite3_str_appendf",\
"sqlite3_value_int64",\
"sqlite3_value_double",\
"sqlite3_value_text",\
"sqlite3_str_reset",\
"sqlite3_str_finish",\
"sqlite3_str_errcode",\
"sqlite3_str_length",\
"sqlite3_str_value",\
"sqlite3_str_new",\
"sqlite3_vmprintf",\
"sqlite3_mprintf",\
"sqlite3_vsnprintf",\
"sqlite3_snprintf",\
"sqlite3_log",\
"sqlite3_randomness",\
"sqlite3_stricmp",\
"sqlite3_strnicmp",\
"sqlite3_os_init",\
"sqlite3_os_end",\
"sqlite3_serialize",\
"sqlite3_prepare_v2",\
"sqlite3_step",\
"sqlite3_column_int64",\
"sqlite3_column_int",\
"sqlite3_finalize",\
"sqlite3_file_control",\
"sqlite3_reset",\
"sqlite3_value_int",\
"sqlite3_deserialize",\
"sqlite3_database_file_object",\
"sqlite3_enable_shared_cache",\
"sqlite3_backup_init",\
"sqlite3_backup_step",\
"sqlite3_backup_finish",\
"sqlite3_backup_remaining",\
"sqlite3_backup_pagecount",\
"sqlite3_expired",\
"sqlite3_clear_bindings",\
"sqlite3_value_blob",\
"sqlite3_value_bytes",\
"sqlite3_value_bytes16",\
"sqlite3_value_subtype",\
"sqlite3_value_pointer",\
"sqlite3_value_text16",\
"sqlite3_value_text16be",\
"sqlite3_value_text16le",\
"sqlite3_value_type",\
"sqlite3_value_encoding",\
"sqlite3_value_nochange",\
"sqlite3_value_frombind",\
"sqlite3_value_dup",\
"sqlite3_value_free",\
"sqlite3_result_blob",\
"sqlite3_result_error_toobig",\
"sqlite3_result_error_nomem",\
"sqlite3_result_blob64",\
"sqlite3_result_double",\
"sqlite3_result_error",\
"sqlite3_result_error16",\
"sqlite3_result_int",\
"sqlite3_result_int64",\
"sqlite3_result_null",\
"sqlite3_result_pointer",\
"sqlite3_result_subtype",\
"sqlite3_result_text",\
"sqlite3_result_text64",\
"sqlite3_result_text16",\
"sqlite3_result_text16be",\
"sqlite3_result_text16le",\
"sqlite3_result_value",\
"sqlite3_result_zeroblob",\
"sqlite3_result_zeroblob64",\
"sqlite3_result_error_code",\
"sqlite3_sql",\
"sqlite3_user_data",\
"sqlite3_context_db_handle",\
"sqlite3_vtab_nochange",\
"sqlite3_vtab_in_first",\
"sqlite3_vtab_in_next",\
"sqlite3_aggregate_context",\
"sqlite3_get_auxdata",\
"sqlite3_set_auxdata",\
"sqlite3_aggregate_count",\
"sqlite3_column_count",\
"sqlite3_data_count",\
"sqlite3_column_blob",\
"sqlite3_column_bytes",\
"sqlite3_column_bytes16",\
"sqlite3_column_double",\
"sqlite3_column_text",\
"sqlite3_column_value",\
"sqlite3_column_text16",\
"sqlite3_column_type",\
"sqlite3_column_name",\
"sqlite3_column_name16",\
"sqlite3_column_decltype",\
"sqlite3_column_decltype16",\
"sqlite3_bind_blob",\
"sqlite3_bind_blob64",\
"sqlite3_bind_double",\
"sqlite3_bind_int",\
"sqlite3_bind_int64",\
"sqlite3_bind_null",\
"sqlite3_bind_pointer",\
"sqlite3_bind_text",\
"sqlite3_bind_text64",\
"sqlite3_bind_text16",\
"sqlite3_bind_value",\
"sqlite3_bind_zeroblob",\
"sqlite3_bind_zeroblob64",\
"sqlite3_bind_parameter_count",\
"sqlite3_bind_parameter_name",\
"sqlite3_bind_parameter_index",\
"sqlite3_transfer_bindings",\
"sqlite3_db_handle",\
"sqlite3_stmt_readonly",\
"sqlite3_stmt_isexplain",\
"sqlite3_stmt_busy",\
"sqlite3_next_stmt",\
"sqlite3_stmt_status",\
"sqlite3_expanded_sql",\
"sqlite3_value_numeric_type",\
"sqlite3_blob_open",\
"sqlite3_errmsg",\
"sqlite3_blob_close",\
"sqlite3_blob_read",\
"sqlite3_blob_write",\
"sqlite3_blob_bytes",\
"sqlite3_blob_reopen",\
"sqlite3_set_authorizer",\
"sqlite3_strglob",\
"sqlite3_strlike",\
"sqlite3_exec",\
"sqlite3_load_extension",\
"sqlite3_enable_load_extension",\
"sqlite3_auto_extension",\
"sqlite3_cancel_auto_extension",\
"sqlite3_reset_auto_extension",\
"sqlite3_prepare",\
"sqlite3_prepare_v3",\
"sqlite3_prepare16",\
"sqlite3_prepare16_v2",\
"sqlite3_prepare16_v3",\
"sqlite3_get_table",\
"sqlite3_free_table",\
"sqlite3_create_module",\
"sqlite3_create_module_v2",\
"sqlite3_drop_modules",\
"sqlite3_declare_vtab",\
"sqlite3_vtab_on_conflict",\
"sqlite3_vtab_config",\
"sqlite3_vtab_collation",\
"sqlite3_vtab_in",\
"sqlite3_vtab_rhs_value",\
"sqlite3_vtab_distinct",\
"sqlite3_keyword_name",\
"sqlite3_keyword_count",\
"sqlite3_keyword_check",\
"sqlite3_complete",\
"sqlite3_complete16",\
"sqlite3_libversion",\
"sqlite3_version",\
"sqlite3_libversion_number",\
"sqlite3_threadsafe",\
"sqlite3_shutdown",\
"sqlite3_data_directory",\
"sqlite3_temp_directory",\
"sqlite3_config",\
"sqlite3_db_mutex",\
"sqlite3_db_release_memory",\
"sqlite3_db_cacheflush",\
"sqlite3_db_config",\
"sqlite3_last_insert_rowid",\
"sqlite3_set_last_insert_rowid",\
"sqlite3_changes64",\
"sqlite3_changes",\
"sqlite3_total_changes64",\
"sqlite3_total_changes",\
"sqlite3_txn_state",\
"sqlite3_close",\
"sqlite3_close_v2",\
"sqlite3_busy_handler",\
"sqlite3_progress_handler",\
"sqlite3_busy_timeout",\
"sqlite3_interrupt",\
"sqlite3_create_function",\
"sqlite3_create_function_v2",\
"sqlite3_create_window_function",\
"sqlite3_create_function16",\
"sqlite3_overload_function",\
"sqlite3_trace",\
"sqlite3_trace_v2",\
"sqlite3_profile",\
"sqlite3_commit_hook",\
"sqlite3_update_hook",\
"sqlite3_rollback_hook",\
"sqlite3_autovacuum_pages",\
"sqlite3_wal_autocheckpoint",\
"sqlite3_wal_hook",\
"sqlite3_wal_checkpoint",\
"sqlite3_wal_checkpoint_v2",\
"sqlite3_error_offset",\
"sqlite3_errmsg16",\
"sqlite3_errcode",\
"sqlite3_extended_errcode",\
"sqlite3_system_errno",\
"sqlite3_errstr",\
"sqlite3_limit",\
"sqlite3_open",\
"sqlite3_free_filename",\
"sqlite3_open_v2",\
"sqlite3_open16",\
"sqlite3_create_collation",\
"sqlite3_create_collation_v2",\
"sqlite3_create_collation16",\
"sqlite3_collation_needed",\
"sqlite3_collation_needed16",\
"sqlite3_global_recover",\
"sqlite3_get_autocommit",\
"sqlite3_thread_cleanup",\
"sqlite3_table_column_metadata",\
"sqlite3_sleep",\
"sqlite3_extended_result_codes",\
"sqlite3_test_control",\
"sqlite3_create_filename",\
"sqlite3_uri_parameter",\
"sqlite3_uri_key",\
"sqlite3_uri_boolean",\
"sqlite3_uri_int64",\
"sqlite3_filename_database",\
"sqlite3_filename_journal",\
"sqlite3_filename_wal",\
"sqlite3_db_name",\
"sqlite3_db_filename",\
"sqlite3_db_readonly",\
"sqlite3_compileoption_used",\
"sqlite3_compileoption_get",\
"sqlite3_rtree_geometry_callback",\
"sqlite3_rtree_query_callback",\
"sqlite3_sourceid",\

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

Still no bueno. Here's my process so far:

I'm using the following PHP script to reproduce the problem:

<?php

// Create (or open) a new SQLite database file
// Use PDO to connect to SQLite database
$dsn = 'sqlite:example3.db';
$db = new SQLite3($dsn);

// Create a new table
$db->exec('CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL
)');

// Insert a few records
$db->exec("INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')");
$db->exec("INSERT INTO users (name, email) VALUES ('Bob', '[email protected]')");
$db->exec("INSERT INTO users (name, email) VALUES ('Charlie', '[email protected]')");

I run it as follows:

PHP=8.3 node --stack-trace-limit=1000  --experimental-strip-types --experimental-transform-types --import ./packages/meta/src/node-es-module-loader/register.mts --watch ./packages/php-wasm/cli/src/main.ts sqlite.php

Also, I've made the following changes to the PHP building Dockerfile:

+	export OPTIMIZATION_FLAGS="-O0 -g3"; \
-	if [ "${WITH_SOURCEMAPS}" = "yes" ] || [ "${WITH_DEBUG}" = "yes" ]; then \
-		# TODO: Revert this to -O0 after debugging.
-		export OPTIMIZATION_FLAGS="-O3"; \
-	fi; \
	PLATFORM_SPECIFIC_ARGS=''; \
	if [ "$EMSCRIPTEN_ENVIRONMENT" = "node" ]; then \
		PLATFORM_SPECIFIC_ARGS="$PLATFORM_SPECIFIC_ARGS --js-library /root/phpwasm-emscripten-library-fcntl-for-node.js"; \
	fi; \
	emcc $OPTIMIZATION_FLAGS \
	--js-library /root/phpwasm-emscripten-library.js \
	--js-library /root/phpwasm-emscripten-library-known-undefined-functions.js \
	$PLATFORM_SPECIFIC_ARGS \
	-I .  \
	-I ext   \
	-I ext/json   \
	-I Zend  \
	-I main  \
	-I TSRM/ \
	-I /root/lib/include \
	-L/root/lib -L/root/lib/lib/ \
	-D__x86_64__\
	-lproxyfs.js \
	$ASYNCIFY_FLAGS \
	$(cat /root/.emcc-php-wasm-flags) \
	-s EXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS" \
	-s WASM_BIGINT=1 \
	-s MIN_NODE_VERSION=200900 \
	-s INITIAL_MEMORY=1024MB \
	-s ALLOW_MEMORY_GROWTH=1 \
+	-s ASSERTIONS=3 \
+	-s ASYNCIFY_STACK_SIZE=1000 \

I've rebuilt as follows:

npm run recompile:php:node:asyncify:8.3

I've extracted all the C function names from the bundled libraries using ctags:

brew install --quiet universal-ctags   # one-time
ctags -x --c-kinds=f your_file.c

First, I downloaded SQLite3 amalgamation from https://www.sqlite.org/download.html and added all those functions to the list. It did not solve the problem:

ctags -x --c-kinds=f ~/Downloads/sqlite-amalgamation-3490100/sqlite3.c | awk $(echo '{print "\"" $1 "\",\\\\"}') | pbcopy

Then I used https://repomix.com/ to get all .c files from the pdo_sqlite PHP extension and extracted all those C functions. It also did not solve the problem.

I've noticed some functions are defined via the PHP_METHOD macro, e.g. PHP_METHOD(Pdo_Sqlite, createFunction). I've started looking for a way to add class methods to the asyncify list. Then I noticed zim_SQLite3_exec in the call stack, which told me the call stack actually goes through the SQLite3 extension functions, not the PDO extension.

I copied sqlite3.c from https://github.com/php/php-src/blob/master/ext/sqlite3/sqlite3.c and added all its functions to the list. I've noticed this line:

PHP_METHOD       function    210 ./many-writes/sqlite3.c PHP_METHOD(SQLite3, exec)

Aha, so SQLite3->exec() is zim_SQLite3_exec in the call stack. I've transformed all the PHP_METHOD macros to zim_ function names and added them to the list, too. That also did not help.

Then I started doubting I'm correctly assessing whether all the functions are present in the Dockerfile so I wrote a small script to confirm I'm right. Functions come through stdin, information comes out through stdout. Sadly, all the functions from the stack trace were already present in the Dockerfile:

import fs from 'fs';
import path from 'path';

const DockerfilePath = path.resolve(
	__dirname,
	'packages/php-wasm/compile/php/Dockerfile'
);

function addAsyncifyFunctionsToDockerfile(functions: string[]) {
	const Dockerfile = fs.readFileSync(DockerfilePath, 'utf8') + '';
	const cleanedUpFunctions = functions.map((candidate) =>
		candidate
			.trim()
			.replace(/^"|^\s*\* php\.wasm\.|"$/g, '')
			.replace('byn$fpcast-emu$', '')
	);

	console.log('Looking for these functions in the Dockerfile:');
	console.log(cleanedUpFunctions);

	const missingCandidates = cleanedUpFunctions.filter(
		(candidate) => !Dockerfile.includes(`"${candidate}"`)
	);
	if (missingCandidates.length) {
		console.log('Missing functions:');
		console.log(missingCandidates.join('\n'));
	} else {
		console.log('All functions are present in the Dockerfile.');
	}
}

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
	input += chunk;
});
process.stdin.on('end', () => {
	const lines = input
		.split('\n')
		.map((line) => line.trim())
		.filter((line) => line.length > 0);
	addAsyncifyFunctionsToDockerfile(lines);
});

Then I thought:

  • Maybe I'm on a wrong branch and not getting some PHP 8.3-specific function?
  • Maybe it's about some core PHP functions or musl functions?

I cloned the php-src repo and switched to PHP-8.3 branch.

I extracted all the extensions-related C functions via ctags -x --c-kinds=f ./ext/**/*.c (in fish shell). I added them to the list and I ran into /bin/bash: argument list too long. I've scaled back to just php-src/main, php-src/Zend, and PDO, PDO SQLite and SQLite3 extensions.

Still no luck!

Next up I'll look into libc/ musl.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

when I stop using ASYNCIFY_ONLY, the error goes away, and based on the log messages, sqlite's calls to fcntl() are effectively blocking until its promise resolves.

I could not reproduce the success yet, but I've managed to trigger the problem via a different code path:

file_get_contents('http://w.org');
RuntimeError: unreachable
    at php.wasm (wasm://wasm/php.wasm-0610d906:wasm-function[11893]:0x169c7ef)
    at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
    at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
    at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:652:12
    at runAndAbortIfError (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8599:16)
    at Object.maybeStopUnwind (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8727:11)
    at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8678:28)
    at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
    at Object.doRewind (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8778:16)
    ... 4 lines matching cause stack trace ...
    at process.processTimers (node:internal/timers:543:7) {
  cause: Error
      at Asyncify.handleSleep (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:10060:49)
      at Object.handleAsync (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8871:25)
      at __syscall_fcntl64 (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:5457:21)
      at imports.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8641:24)
      at php.wasm.fcntl (wasm://wasm/php.wasm-0610d906:wasm-function[11335]:0x1664521)
      at php.wasm.php_network_connect_socket (wasm://wasm/php.wasm-0610d906:wasm-function[1896]:0x4419c7)
      at php.wasm.php_network_connect_socket_to_host (wasm://wasm/php.wasm-0610d906:wasm-function[1904]:0x4450d2)
      at php.wasm.php_tcp_sockop_set_option (wasm://wasm/php.wasm-0610d906:wasm-function[1891]:0x43e964)
      at php.wasm._php_stream_set_option (wasm://wasm/php.wasm-0610d906:wasm-function[8566]:0x11e52fc)
      at php.wasm.dynCall_iiiii (wasm://wasm/php.wasm-0610d906:wasm-function[11837]:0x1698579)
      at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
      at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
      at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:652:12
      at invoke_iiiii (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:9525:12)
      at imports.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8641:24)
      at php.wasm._php_stream_xport_create (wasm://wasm/php.wasm-0610d906:wasm-function[798]:0x18cad9)
      at php.wasm.php_stream_url_wrap_http_ex (wasm://wasm/php.wasm-0610d906:wasm-function[1929]:0x44c05e)
      at php.wasm.php_stream_url_wrap_http (wasm://wasm/php.wasm-0610d906:wasm-function[1928]:0x44a659)
      at php.wasm._php_stream_open_wrapper_ex (wasm://wasm/php.wasm-0610d906:wasm-function[8599]:0x11efc5c)
      at php.wasm.zif_file_get_contents (wasm://wasm/php.wasm-0610d906:wasm-function[8496]:0x11c8f1d)
      at php.wasm.phar_file_get_contents (wasm://wasm/php.wasm-0610d906:wasm-function[7650]:0x102603d)
      at php.wasm.ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER (wasm://wasm/php.wasm-0610d906:wasm-function[9710]:0x13aa0d0)
      at php.wasm.execute_ex (wasm://wasm/php.wasm-0610d906:wasm-function[8930]:0x12613f4)
      at php.wasm.zend_execute (wasm://wasm/php.wasm-0610d906:wasm-function[8933]:0x1261fe7)
      at php.wasm.zend_execute_scripts (wasm://wasm/php.wasm-0610d906:wasm-function[10209]:0x143d602)
      at php.wasm.dynCall_iiiii (wasm://wasm/php.wasm-0610d906:wasm-function[11837]:0x1698579)
      at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
      at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
      at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:652:12
      at invoke_iiiii (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:9525:12)
      at imports.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8641:24)
      at php.wasm.php_execute_script (wasm://wasm/php.wasm-0610d906:wasm-function[8437]:0x11b73f8)
      at php.wasm.dynCall_ii (wasm://wasm/php.wasm-0610d906:wasm-function[11832]:0x1698051)
      at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
      at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
      at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:652:12
      at invoke_ii (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:9437:12)
      at imports.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8641:24)
      at php.wasm.do_cli (wasm://wasm/php.wasm-0610d906:wasm-function[11221]:0x164e2d3)
      at php.wasm.dynCall_iii (wasm://wasm/php.wasm-0610d906:wasm-function[11836]:0x169844c)
      at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
      at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
      at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:652:12
      at invoke_iii (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:9470:12)
      at imports.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8641:24)
      at php.wasm.main (wasm://wasm/php.wasm-0610d906:wasm-function[11216]:0x163a3e9)
      at php.wasm.run_cli (wasm://wasm/php.wasm-0610d906:wasm-function[10713]:0x153cc76)
      at ret.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8673:24)
      at runtime.wasmExports.<computed> (/Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts:43:13)
      at Object.doRewind (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8778:16)
      at file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:8812:47
      at callUserCallback (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:6930:9)
      at Timeout._onTimeout (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/asyncify/php_8_3.js:7138:9)
      at listOnTimeout (node:internal/timers:608:17)
      at process.processTimers (node:internal/timers:543:7)
}
Aborted(RuntimeError: unreachable)

This is useful because it points to a problem outside of the SQLite3 library.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

Aha, it worked with -s ASYNCIFY_STACK_SIZE=50000 !

Okay. Your comments from yesterday gave me an idea @brandonpayton. I'll try to get a list of the automatically enlisted functions by setting -s ASYNCIFY_ADVISE=1. Maybe we could do some form of binary search from there to identify the offending entries.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

This Dockerfile finally yielded a build that successfully ran my test PHP script!

https://gist.github.com/adamziel/e4d85b7575787f684b7f154f45d0741e

I used the list of functions produced by ASYNCIFY_ADVISE. Turns out it was missing a bunch of items such as wasm_sleep and zif_sleep so I've combined it with the list we've already had. It still crashed with our familiar unreachable error and at that point I lost hope. As a formality, I fed the stacktrace into the "is every item in Dockerfile?" script I've posted above and... I got a few hits!

I added the newly found functions to Dockerfile and rebuilt PHP. This time I got different hits. After repeating this a few times, I got a working build. Yay!

We should automate that process for other PHP versions and SQLite3/PDO SQLite code paths. We have this commant npm run fix-asyncify that runs a few dedicated test suites, observes the stack traces, looks for all the missing ASYNCIFY functions across all the PHP versions, updates the Dockerfile, rebuilds PHP, and iterates until either everything passes or we get a failure that doesn't reveal any missing function names. We should add SQLite-specific tests there.

This is the script that orchestrates it all:

https://github.com/WordPress/wordpress-playground/blob/trunk/packages/php-wasm/node/bin/rebuild-while-asyncify-functions-missing.mjs

The Dockerfile is updated inside the specific unit test files. I know, I know, it smells bad. But it solves the problem pretty well.

We'll also likely want to remove a lot of functions from that Dockerfile. But still – yay!

PHP 8.3 build is 14M. I'm not sure why not 17M like 8.2 build, maybe some extensions are disabled? In either case, we can work with this from here.

@adamziel
Copy link
Collaborator

adamziel commented Jun 7, 2025

Looping in @mho22 for inspiration - this discussion may be helpful in the "XDebug on Asyncify" project. Let's still ship the JSPI version first.

@adamziel
Copy link
Collaborator

adamziel commented Jun 8, 2025

I got it to work on all PHP versions. I just pushed my changes to this branch.

@brandonpayton
Copy link
Member Author

Amazing, @adamziel! Thank you very much. It's a huge help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Inbox
Development

Successfully merging this pull request may close these issues.

2 participants