-
Notifications
You must be signed in to change notification settings - Fork 313
Support multiple workers for NODEFS /wordpress mounts – Asyncify #2317
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
Conversation
I am encountering a null-or-signature-mismatch error here: The line is this, the ZEND_API void ZEND_FASTCALL rc_dtor_func(zend_refcounted *p)
{
ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST);
zend_rc_dtor_func[GC_TYPE(p)](p);
} I was able to find the GC_TYPE by expanding the refcounted pointer struct while debugging in Cursor. The GH type num was 7, which is an offset in this array: static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
[IS_UNDEF] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_NULL] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_FALSE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_TRUE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_LONG] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_DOUBLE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_STRING] = (zend_rc_dtor_func_t)zend_string_destroy,
[IS_ARRAY] = (zend_rc_dtor_func_t)zend_array_destroy,
[IS_OBJECT] = (zend_rc_dtor_func_t)zend_objects_store_del,
[IS_RESOURCE] = (zend_rc_dtor_func_t)zend_list_free,
[IS_REFERENCE] = (zend_rc_dtor_func_t)zend_reference_destroy,
[IS_CONSTANT_AST] = (zend_rc_dtor_func_t)zend_ast_ref_destroy
}; If the GC type is indeed 7, then it looks like the dtor is |
Any chance it would work on trunk with @mho22 wrapper? Or was that a completely different code path? |
It might! I think it is a different path, but it feels like it could be a similar thing. Will try shortly. :) |
After merging trunk into this branch, I'm still seeing the same issue. Will instrument php-src and see what can be found. |
@adamziel @brandonpayton For the record, my wrapper is only targeting PHP7.4 and PHP7.3 to replace the old
It may be the same issue but Brandon is right, that's a new one. And the
But maybe not, I think
So maybe you'll need to apply a new patch [ to PHP8.4 and PHP8.3 at least ] like this : + static void zend_array_destroy_wrapper(zend_refcounted *p) {
+ HashTable *ht = (HashTable*)p;
+ zend_array_destroy(ht);
+}
static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
[IS_UNDEF] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_NULL] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_FALSE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_TRUE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_LONG] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_DOUBLE] = (zend_rc_dtor_func_t)zend_empty_destroy,
[IS_STRING] = (zend_rc_dtor_func_t)zend_string_destroy,
- [IS_ARRAY] = (zend_rc_dtor_func_t)zend_array_destroy,
+ [IS_ARRAY] = zend_array_destroy_wrapper
[IS_OBJECT] = (zend_rc_dtor_func_t)zend_objects_store_del,
[IS_RESOURCE] = (zend_rc_dtor_func_t)zend_list_free,
[IS_REFERENCE] = (zend_rc_dtor_func_t)zend_reference_destroy,
[IS_CONSTANT_AST] = (zend_rc_dtor_func_t)zend_ast_ref_destroy
}; I hope this helps. |
Thank you, @mho22. 🙇 This discussion is helpful. Hopefully, we are close to where the problem is. I took a look at the HashTable and zend_array types, and they are both type aliases for the same struct like:
My mental model suggests those types are equivalent and would not conflict, but it would be good to try a wrapper to confirm. Also, maybe there is some other type that is marked with IS_ARRAY that is not a HashTable or zend_array... We'll see! |
Note: I am still getting the issue with the wrapper, but debugging has gotten a little easier: I headed to AFK soon but plan to spend more time on this in the morning. |
I added a lot more logging in php-src to try to track this but then started having "null or signature mismatch" errors with the logging. 🤦 But I'm learning some things and continuing to dig. I wanted to step into the |
Does it also fail in the same way on PHP 7.3 and 7.4? If not, maybe we can learn something from the differences in source? |
Oh, interesting! I thought the error happened at the moment of a mismatched call – am I understanding it wrong? |
Would safe_heap_log or assertions=2 reveal anything else? Also, would instrumenting the pointer casts internals reveal anything about the wrappers generated for these destructors? https://emscripten.org/docs/tools_reference/settings_reference.html?utm_source=perplexity |
Also -sEMSCRIPTEN_TRACING, -sSTRICT, -sSIGNATURE_CONVERSIONS |
Thanks, @adamziel! Those all sound like good suggestions, and I plan to try them next.
I don't know for sure, but your thinking matches my mental model. I think the error may have started occurring in an additional location when I added code that logged to a file. (I tried to use the |
As a note regarding our pursuit of more/easier debugging:
where DETERMINISTIC_PREFIX is a constant set to "/emsdk/emscripten". There is not currently a way to override this, but perhaps the Emscripten team would consider allowing an override with an environment var or other setting. We could also explore post-processing DWARF info in wasm, but it would likely involve writing code to do that. So far, I have not found good tools for it. Complete source debugging is not required to solve this problem, but it would be amazing to be able to step into any function that is failing. |
There is this Rust lib: Some libs like this seem to expect elf binaries but this one says:
So it should be able to just process the DWARF debug info if it can be read from custom WebAssembly sections. Note how the debug info is placed in custom sections in the a small .wasm lib I build with Emscripten:
|
I wonder if our override of proc_open.c and proc_open.h causes the bulk of these issues. We use these two, 7.4-specific, files for every PHP version from 7.4 to 8.4 and they don't account for any changes in PHP core 🤔 At the same time, for this specific issue PHP 7.4 still fails with the same error as 8.0 and it's not a problem with JSPI. |
…de-es-module-loader/loader.mts ./packages/php-wasm/cli/src/main.ts test.php`
I found two more functions that were at the callstack during the crash but not on the ayncify list (for PHP 7.4):
Adding them to the asyncify list did not resolve the problem. |
de87c6f
to
0e5387b
Compare
79ad795
to
ea4cb3c
Compare
The tests are all passing, let's do it! |
…nc.ts (#2363) ## Motivation for the change, related issues #2317 introduced a dynamic import in a CJS code path (`comlink-sync.ts`). Node 20 requires a `--experimental-vm-modules` flag to run dynamic imports in such a context. This PR tries calling `require()` first to make it all work again. ## Testing Instructions (or ideally a Blueprint) Confirm the `test-built-npm-packages` test passed.
Nice job, @adamziel! Using |
Also, thank you for all your help and for closing out this PR! 🙇 |
This is a cool idea. Atomics.pause() offers an interesting possibility for compromise: Less burdensome busy waiting.
@adamziel, contrary to what I said our offline discussion earlier today, it looks like this could be workable for us. If I understand the following tests correctly, Service Worker...
Since php-wasm/web is run in worker threads, it seems like we could use sync xhr for blocking if needed. |
Overview
Adds multi-worker support for Node.js Asyncify builds to complement JSPI support and enable usage in Node < v23. Note this doesn't work with Asyncify in web browsers.
This PR also adds
fileLockManager
support for@php-wasm/cli
Implementation
This PR is different from other Asyncify PRs in that it doesn't actually make things work with Asyncify. Instead, it switches to synchronous message passing when JSPI is unavailable. This way, the Asyncify builds never have to engage in stack switching around
fd_close()
orfcntl()
. This wasn't the first choice, but getting the Asyncify builds right was just too challenging, so we had to use another approach.Synchronous message passing via Comlink
Important
This PR forks the Comlink library to add
afterResponseSent?: (ev: MessageEvent) => void
argument to theexpose()
function. The rest is unchanged. Comlink isn't getting many new PRs so skipping updates (or backporting occasionally) seems fine.Playground already uses Comlink for RPC between workers. This PR adds synchronous bindings via
exposeSync
andwrapSync
.The specific technique of exchanging messages is described in https://github.com/adamziel/js-synchronous-messaging:
postMessage()
on Worker B's MessagePort to start the RPC exchange.SharedArrayBuffer
) to synchronously wait for a notification from the Worker B.For usage example, see comlink-sync.spec.ts.
The upsides of this approach:
The downsides:
it requires more restrictive CORP+COEP headers which breaks, e.g., YouTube embeds. Synchronous XHR
might work if we really need Safari support for one of the new asynchronous features, but other than
that let's just skip adding new asynchronous WASM features to Safari until WebKit supports stack switching.
Dual channel support
The Emscripten-built php.js requires either:
fileLockManager
– when JSPI is availablefileLockManager
– when JSPI is not availableThis is implemented with preprocessor directives, e.g.
Why support both methods and not always use synchronous calls? Because web browsers have no
receiveMessageOnPort
and can only handle asynchronous message passing. Supporting both sync and async message channels provides maximum compatibility. The only environment wherefileLockManager
is not supported is Safari.Testing Instructions (or ideally a Blueprint)
Playground CLI
Run Playground CLI server with 5 workers using Asyncify:
Create some posts, install some plugins, confirm it does not crash.
Then do the same with JSPI and confirm everything continues to work:
PHP.wasm CLI
Run the test script below and confirm it does not crash or corrupt the database:
test.php