diff --git a/packages/php-wasm/compile/php/Dockerfile b/packages/php-wasm/compile/php/Dockerfile index 42198118f4..ec16ee73d1 100644 --- a/packages/php-wasm/compile/php/Dockerfile +++ b/packages/php-wasm/compile/php/Dockerfile @@ -712,6 +712,7 @@ RUN export ASYNCIFY_IMPORTS=$'[\n\ "cli",\ "close_stmt_and_copy_errors",\ "close",\ +"__stdio_close",\ "closeUnixFile",\ "compile_file",\ "createCollation",\ diff --git a/packages/php-wasm/compile/php/php_wasm.c b/packages/php-wasm/compile/php/php_wasm.c index f2701922fe..c716181bf8 100644 --- a/packages/php-wasm/compile/php/php_wasm.c +++ b/packages/php-wasm/compile/php/php_wasm.c @@ -154,8 +154,14 @@ EM_JS(int, wasm_poll_socket, (php_socket_t socketd, int events, int timeout), { * Check for socket-ness first. We don't clean up child_proc_by_fd yet and * sometimes get duplicate entries. isSocket is more reliable out of the two – * let's check for it first. + * + * @TODO: Remove this code branch entirely and poll sockets using the same poll() + * syscall as we use for other streams. The only reason this wasn't done is + * that the original PR was focusing on removing child process-related special + * casing and did not want to increase the risk of breaking other code. */ - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { // This is, most likely, a websocket. Let's make sure. const sock = getSocketFromFD(socketd); if (!sock) { @@ -208,15 +214,44 @@ EM_JS(int, wasm_poll_socket, (php_socket_t socketd, int events, int timeout), { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - // This is a child process-related socket. - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); - return; - } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); - } else { + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); + return; + } + // Poll the stream for data. + let interrupted = false; + async function poll() { + try { + while (true) { + // Inlined ___syscall_poll + var mask = 32; // {{{ cDefine('POLLNVAL') }}}; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + + mask &= events | 8 | 16; // | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise(resolve => setTimeout(resolve, 10)); + } + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + } + ]); + } else { setTimeout(function () { wakeUp(1); }, timeout); @@ -279,14 +314,12 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si const returnCallback = (resolver) => Asyncify.handleSleep(resolver); #endif if (Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + // How many bytes did we read? + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) @@ -296,26 +329,36 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si { throw e; } - // Only return synchronously if this isn't an asynchronous pipe. - // Error code 6 indicates EWOULDBLOCK – this is our signal to wait. - // We also need to distinguish between a process pipe and a file pipe, otherwise - // reading from an empty file would block until the timeout. - if (e.errno !== 6 || !(stream?.fd in PHPWASM.child_proc_by_fd)) - { - // On failure, yield 0 bytes read to indicate EOF. + const isBlockingFdThatWaitsForData = ( + // is blocking? + !(stream.flags & locking.O_NONBLOCK) && + // is waiting for data? + e.errno === ERRNO_CODES.EWOULDBLOCK && + // if it's a pipe, does it have a living other end? + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2) + ); + /** + * The only reason to fall through to polling is if we're processing + * a blocking pipe that's still waiting for data. + * + * In every other case, we should tell the caller we've ran into an error. + */ + if(!isBlockingFdThatWaitsForData) { + // Indicate 0 bytes read. HEAPU32[pnum >> 2] = 0; - return returnCode + return e.errno; } } } - // At this point we know we have to poll. - // You might wonder why we duplicate the code here instead of always using + // At this point we're certain we need to poll. + // + // You might wonder why we duplicate the code here instead of just reusing // Asyncify.handleSleep(). The reason is performance. Most of the time, // the read operation will work synchronously and won't require yielding // back to JS. In these cases we don't want to pay the Asyncify overhead, // save the stack, yield back to JS, restore the stack etc. - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5000; @@ -324,7 +367,7 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si // to, say, block the entire PHPUnit test suite without any visible // feedback. var maxRetries = timeout / interval; - function poll() { + while(true) { var returnCode; var stream; let num; @@ -343,28 +386,30 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si returnCode = e.errno; } - const success = returnCode === 0; - const failure = ( - ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream) - ); + // read succeeded! + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } - if (success) { + if ( + // Too many retries? That's an error, too! + ++retries > maxRetries || + // Stream closed? That's an error. + !stream || FS.isClosed(stream) || + // Error different than EWOULDBLOCK – propagate it to the caller. + returnCode !== ERRNO_CODES.EWOULDBLOCK || + // Broken pipe + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - // On failure, yield 0 bytes read to indicate EOF. - HEAPU32[pnum >> 2] = 0; - // If the failure is due to a timeout, return 0 to indicate that we - // reached EOF. Otherwise, propagate the error code. - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + + // It's a blocking stream and we Blocking stream with no data available yet. + // Let's poll up to a timeout. + await new Promise(resolve => setTimeout(resolve, interval)); } - poll(); }) }); extern int __wasi_syscall_ret(__wasi_errno_t code); diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js index 8fabdebc09..db98e16247 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library-file-locking-for-node.js @@ -23,6 +23,20 @@ const LibraryForFileLocking = { F_RDLCK: 0, F_WRLCK: 1, F_UNLCK: 2, + /** + * @see fcntl.c: + * https://github.com/torvalds/linux/blob/a79a588fc1761dc12a3064fc2f648ae66cea3c5a/fs/fcntl.c#L37 + */ + O_APPEND: Number('{{{cDefs.O_APPEND}}}'), + O_NONBLOCK: Number('{{{cDefs.O_NONBLOCK}}}'), + SETFL_MASK: + Number('{{{cDefs.O_APPEND}}}') | + Number('{{{cDefs.O_NONBLOCK}}}') + // These macros are not defined in Emscripten at the time of writing: + // emscripten_O_NDELAY | + // emscripten_O_DIRECT | + // emscripten_O_NOATIME + , lockStateToFcntl: { shared: 0, exclusive: 1, @@ -40,7 +54,10 @@ const LibraryForFileLocking = { } // Handle PROXYFS nodes which wrap other nodes. - if (!node?.mount?.opts?.fs?.lookupPath || !node?.mount?.type?.realPath) { + if ( + !node?.mount?.opts?.fs?.lookupPath || + !node?.mount?.type?.realPath + ) { return false; } @@ -50,7 +67,8 @@ const LibraryForFileLocking = { } const vfsPath = node.mount.type.realPath(node); try { - const underlyingNode = node.mount.opts.fs.lookupPath(vfsPath)?.node; + const underlyingNode = + node.mount.opts.fs.lookupPath(vfsPath)?.node; return !!underlyingNode?.isSharedFS; } catch (e) { return false; @@ -116,11 +134,14 @@ const LibraryForFileLocking = { SYSCALLS.varargs = varargs; // These constants are replaced by Emscripten during the build process + const emscripten_F_SETFL = Number('{{{cDefs.F_SETFL}}}'); const emscripten_F_GETLK = Number('{{{cDefs.F_GETLK}}}'); const emscripten_F_SETLK = Number('{{{cDefs.F_SETLK}}}'); const emscripten_F_SETLKW = Number('{{{cDefs.F_SETLKW}}}'); const emscripten_SEEK_SET = Number('{{{cDefs.SEEK_SET}}}'); + + // NOTE: With the exception of l_type, these offsets are not exposed to // JS by Emscripten, so we hardcode them here. const emscripten_flock_l_type_offset = 0; @@ -511,6 +532,38 @@ const LibraryForFileLocking = { // because it is a known errno for a failed F_SETLKW command. return -ERRNO_CODES.EDEADLK; } + case emscripten_F_SETFL: { + /** + * Overrides the core Emscripten implementation to reflect what + * fcntl does in linux kernel. This implementation is still missing + * a bunch of nuance, but, unlike the core Emscripten implementation, + * it overrides the stream flags while preserving non-stream flags. + * + * @see fcntl.c: + * https://github.com/torvalds/linux/blob/a79a588fc1761dc12a3064fc2f648ae66cea3c5a/fs/fcntl.c#L39 + */ + const arg = SYSCALLS.get(); + const stream = SYSCALLS.getStreamFromFD(fd); + + // Get current flags + const currentFlags = stream.flags; + + // Required for strict SunOS emulation + if (emscripten_O_NONBLOCK !== emscripten_O_NDELAY) { + if (arg & emscripten_O_NDELAY) { + arg |= emscripten_O_NONBLOCK; + } + } + + // Pipe packetized mode is controlled by O_DIRECT flag + // We don't have S_ISFIFO or FMODE_CAN_ODIRECT checks in our implementation + // so we skip this validation + + // Update the stream flags + stream.flags = (arg & locking.SETFL_MASK) | (currentFlags & ~locking.SETFL_MASK); + + return 0; + } default: return _builtin_fcntl64(fd, cmd, varargs); } diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js index 2e13322145..d5ab7096d0 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js @@ -126,14 +126,6 @@ const LibraryExample = { } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; @@ -339,7 +331,7 @@ const LibraryExample = { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, + // timeout: 100, }); } const e = new Error( @@ -530,14 +522,6 @@ const LibraryExample = { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { diff --git a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm index d3521238c9..7d0f1f4ce5 100755 Binary files a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm and b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm index 701ccc569f..0864555b73 100755 Binary files a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm and b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm index 371e791859..e6fba7f82c 100755 Binary files a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm and b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm index e45ae07bcc..1afcb97da0 100755 Binary files a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm and b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm b/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm index 2db7f28a65..be0155bbd0 100755 Binary files a/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm and b/packages/php-wasm/node/asyncify/8_1_23/php_8_1.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm b/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm index f608ad718f..fc71f9c4f7 100755 Binary files a/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm and b/packages/php-wasm/node/asyncify/8_2_10/php_8_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm b/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm index e0929e1472..533f393883 100755 Binary files a/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm and b/packages/php-wasm/node/asyncify/8_3_0/php_8_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm b/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm index 341010aff5..3743ee4a97 100755 Binary files a/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm and b/packages/php-wasm/node/asyncify/8_4_0/php_8_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/php_7_2.js b/packages/php-wasm/node/asyncify/php_7_2.js index fdd04add7b..1b28fa56ee 100644 --- a/packages/php-wasm/node/asyncify/php_7_2.js +++ b/packages/php-wasm/node/asyncify/php_7_2.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '7_2_34', 'php_7_2.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 17703689; +export const dependenciesTotalSize = 17703596; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7112,13 +7112,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7269,7 +7262,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7421,14 +7413,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8420,7 +8404,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8468,13 +8453,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8524,33 +8542,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8568,23 +8584,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_7_3.js b/packages/php-wasm/node/asyncify/php_7_3.js index 089f82310f..baf3fafe92 100644 --- a/packages/php-wasm/node/asyncify/php_7_3.js +++ b/packages/php-wasm/node/asyncify/php_7_3.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '7_3_33', 'php_7_3.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 17697847; +export const dependenciesTotalSize = 17697884; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7112,13 +7112,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7269,7 +7262,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7421,14 +7413,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8420,7 +8404,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8468,13 +8453,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8524,33 +8542,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8568,23 +8584,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_7_4.js b/packages/php-wasm/node/asyncify/php_7_4.js index 448a7879d1..2dd80e20fa 100644 --- a/packages/php-wasm/node/asyncify/php_7_4.js +++ b/packages/php-wasm/node/asyncify/php_7_4.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '7_4_33', 'php_7_4.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 18164135; +export const dependenciesTotalSize = 18164175; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7112,13 +7112,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7269,7 +7262,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7421,14 +7413,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8424,7 +8408,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8472,13 +8457,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8528,33 +8546,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8572,23 +8588,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_8_0.js b/packages/php-wasm/node/asyncify/php_8_0.js index a9eb1dd49d..ec39cfece5 100644 --- a/packages/php-wasm/node/asyncify/php_8_0.js +++ b/packages/php-wasm/node/asyncify/php_8_0.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_0_30', 'php_8_0.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 18445623; +export const dependenciesTotalSize = 18445673; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7112,13 +7112,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7269,7 +7262,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7421,14 +7413,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8424,7 +8408,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8472,13 +8457,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8528,33 +8546,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8572,23 +8588,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_8_1.js b/packages/php-wasm/node/asyncify/php_8_1.js index b1f4661d84..f10b15fd25 100644 --- a/packages/php-wasm/node/asyncify/php_8_1.js +++ b/packages/php-wasm/node/asyncify/php_8_1.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_1_23', 'php_8_1.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 18446910; +export const dependenciesTotalSize = 18446960; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7140,13 +7140,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7297,7 +7290,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7449,14 +7441,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8456,7 +8440,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8504,13 +8489,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8560,33 +8578,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8604,23 +8620,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_8_2.js b/packages/php-wasm/node/asyncify/php_8_2.js index 1aeb11e854..6eb8bc3fab 100644 --- a/packages/php-wasm/node/asyncify/php_8_2.js +++ b/packages/php-wasm/node/asyncify/php_8_2.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_2_10', 'php_8_2.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 18802780; +export const dependenciesTotalSize = 18802804; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7142,13 +7142,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7299,7 +7292,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7451,14 +7443,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8458,7 +8442,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8506,13 +8491,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8562,33 +8580,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8606,23 +8622,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_8_3.js b/packages/php-wasm/node/asyncify/php_8_3.js index 38f74cffae..5e58716d1f 100644 --- a/packages/php-wasm/node/asyncify/php_8_3.js +++ b/packages/php-wasm/node/asyncify/php_8_3.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_3_0', 'php_8_3.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 19201580; +export const dependenciesTotalSize = 19201631; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7142,13 +7142,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7299,7 +7292,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7451,14 +7443,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8458,7 +8442,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8506,13 +8491,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8562,33 +8580,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8606,23 +8622,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/asyncify/php_8_4.js b/packages/php-wasm/node/asyncify/php_8_4.js index 11f8821eac..8a7433463c 100644 --- a/packages/php-wasm/node/asyncify/php_8_4.js +++ b/packages/php-wasm/node/asyncify/php_8_4.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_4_0', 'php_8_4.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 22582127; +export const dependenciesTotalSize = 22582165; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js // include: shell.js @@ -7142,13 +7142,6 @@ export function init(RuntimeName, PHPLoader) { } } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; const originalWrite = TTY.stream_ops.write; @@ -7299,7 +7292,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -7451,14 +7443,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { for (const fd of [ @@ -8458,7 +8442,8 @@ export function init(RuntimeName, PHPLoader) { const POLLNVAL = 32; return returnCallback((wakeUp) => { const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { const sock = getSocketFromFD(socketd); if (!sock) { wakeUp(0); @@ -8506,13 +8491,46 @@ export function init(RuntimeName, PHPLoader) { lookingFor.add('POLLERR'); } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); + } else if (stream?.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = 32; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | 8 | 16; + if (mask) { + return mask; + } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { wakeUp(1); @@ -8562,33 +8580,31 @@ export function init(RuntimeName, PHPLoader) { Asyncify?.State?.Normal === undefined || Asyncify?.state === Asyncify?.State?.Normal ) { - var returnCode; var stream; - let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { throw e; } - if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) - ) { + const isBlockingFdThatWaitsForData = + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || stream.node.pipe.refcnt >= 2); + if (!isBlockingFdThatWaitsForData) { HEAPU32[pnum >> 2] = 0; - return returnCode; + return e.errno; } } } - return returnCallback((wakeUp) => { + return returnCallback(async (wakeUp) => { var retries = 0; var interval = 50; var timeout = 5e3; var maxRetries = timeout / interval; - function poll() { + while (true) { var returnCode; var stream; let num; @@ -8606,23 +8622,22 @@ export function init(RuntimeName, PHPLoader) { } returnCode = e.errno; } - const success = returnCode === 0; - const failure = + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + await new Promise((resolve) => setTimeout(resolve, interval)); } - poll(); }); } diff --git a/packages/php-wasm/node/jspi/php_8_4.js b/packages/php-wasm/node/jspi/php_8_4.js index 225e330eac..617038299a 100644 --- a/packages/php-wasm/node/jspi/php_8_4.js +++ b/packages/php-wasm/node/jspi/php_8_4.js @@ -5624,7 +5624,12 @@ export function init(RuntimeName, PHPLoader) { if (sock.type === 1 && sock.server) { // listen sockets should only say they're available for reading // if there are pending clients. - return sock.pending.length ? 64 | 1 : 0; + for (const client of sock.pending) { + if ((client.recv_queue || []).length > 0) { + return 1; + } + } + return 0; } var mask = 0; @@ -5651,7 +5656,8 @@ export function init(RuntimeName, PHPLoader) { !dest || // connection-less sockets are always ready to write (dest && dest.socket.readyState === dest.socket.OPEN) ) { - mask |= 4; + // console.log('open!'); + // mask |= 4; } if ( @@ -6694,14 +6700,6 @@ export function init(RuntimeName, PHPLoader) { } }; - // Clean up the fd -> childProcess mapping when the fd is closed: - const originalClose = FS.close; - FS.close = function (stream) { - originalClose(stream); - delete PHPWASM.child_proc_by_fd[stream.fd]; - }; - - PHPWASM.child_proc_by_fd = {}; PHPWASM.child_proc_by_pid = {}; PHPWASM.input_devices = {}; @@ -6849,7 +6847,6 @@ export function init(RuntimeName, PHPLoader) { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'], - timeout: 100, }); } const e = new Error( @@ -17479,14 +17476,6 @@ export function init(RuntimeName, PHPLoader) { stdout: new PHPWASM.EventEmitter(), stderr: new PHPWASM.EventEmitter(), }; - if (ProcInfo.stdoutChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutChildFd] = ProcInfo; - if (ProcInfo.stderrChildFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrChildFd] = ProcInfo; - if (ProcInfo.stdoutParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stdoutParentFd] = ProcInfo; - if (ProcInfo.stderrParentFd) - PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo; PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo; cp.on('exit', function (code) { @@ -31265,65 +31254,57 @@ export function init(RuntimeName, PHPLoader) { const POLLHUP = 0x0010; const POLLNVAL = 0x0020; return returnCallback((wakeUp) => { + const stream = FS.getStream(socketd); + const polls = []; - if (FS.isSocket(FS.getStream(socketd)?.node.mode)) { - const sock = getSocketFromFD(socketd); - if (!sock) { - wakeUp(0); + if (stream.stream_ops?.poll) { + if (!stream) { + wakeUp(-1); return; } - const lookingFor = new Set(); - if (events & POLLIN || events & POLLPRI) { - if (sock.server) { - for (const client of sock.pending) { - if ((client.recv_queue || []).length > 0) { - wakeUp(1); - return; + // Poll the stream for data. + let interrupted = false; + async function poll() { + try { + while (true) { + // Inlined ___syscall_poll + var mask = 32; // {{{ cDefine('POLLNVAL') }}}; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + + mask &= events | 8 | 16; // | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + // console.log('polling', mask); + if (mask) { + // console.log(stream.node); + return mask; } + if (interrupted) { + return 0; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); } - } else if ((sock.recv_queue || []).length > 0) { - wakeUp(1); - return; - } - } - const webSockets = PHPWASM.getAllWebSockets(sock); - if (!webSockets.length) { - wakeUp(0); - return; - } - for (const ws of webSockets) { - if (events & POLLIN || events & POLLPRI) { - polls.push(PHPWASM.awaitData(ws)); - lookingFor.add('POLLIN'); - } - if (events & POLLOUT) { - polls.push(PHPWASM.awaitConnection(ws)); - lookingFor.add('POLLOUT'); - } - if ( - events & POLLHUP || - events & POLLIN || - events & POLLOUT || - events & POLLERR - ) { - polls.push(PHPWASM.awaitClose(ws)); - lookingFor.add('POLLHUP'); - } - if (events & POLLERR || events & POLLNVAL) { - polls.push(PHPWASM.awaitError(ws)); - lookingFor.add('POLLERR'); + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; } } - } else if (socketd in PHPWASM.child_proc_by_fd) { - const procInfo = PHPWASM.child_proc_by_fd[socketd]; - if (procInfo.exited) { - wakeUp(0); - return; - } - polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data')); + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); } else { setTimeout(function () { - wakeUp(1); + wakeUp(-1); }, timeout); return; } @@ -31378,8 +31359,7 @@ export function init(RuntimeName, PHPLoader) { let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); - const num = doReadv(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); return 0; } catch (e) { if ( @@ -31389,9 +31369,14 @@ export function init(RuntimeName, PHPLoader) { throw e; } if ( - e.errno !== 6 || - !(stream?.fd in PHPWASM.child_proc_by_fd) + !(stream.flags & locking.O_NONBLOCK) && + e.errno === ERRNO_CODES.EWOULDBLOCK && + (!('pipe' in stream.node) || + stream.node.pipe.refcnt === 2) ) { + // Blocking fd that waits for data? Fall through to polling. + } else { + // Otherwise that's an error. HEAPU32[pnum >> 2] = 0; return returnCode; } @@ -31405,7 +31390,7 @@ export function init(RuntimeName, PHPLoader) { function poll() { var returnCode; var stream; - let num; + let num = 0; try { stream = SYSCALLS.getStreamFromFD(fd); num = doReadv(stream, iov, iovcnt); @@ -31418,23 +31403,33 @@ export function init(RuntimeName, PHPLoader) { console.error(e); throw e; } + returnCode = e.errno; } - const success = returnCode === 0; - const failure = + + // read succeeded! + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + + if ( + // Too many retries? That's an error, too! ++retries > maxRetries || - !(fd in PHPWASM.child_proc_by_fd) || - PHPWASM.child_proc_by_fd[fd]?.exited || - FS.isClosed(stream); - if (success) { + // Stream closed? That's an error. + !stream || + FS.isClosed(stream) || + // Error different than EWOULDBLOCK – propagate it to the caller. + returnCode !== ERRNO_CODES.EWOULDBLOCK || + // Broken pipe + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { HEAPU32[pnum >> 2] = num; - wakeUp(0); - } else if (failure) { - HEAPU32[pnum >> 2] = 0; - wakeUp(returnCode === 6 ? 0 : returnCode); - } else { - setTimeout(poll, interval); + return wakeUp(returnCode); } + + // Otherwise, keep trying. + setTimeout(poll, interval); } poll(); }); diff --git a/packages/php-wasm/node/src/test/php.spec.ts b/packages/php-wasm/node/src/test/php.spec.ts index 94a54d02ab..d8134a6b9d 100644 --- a/packages/php-wasm/node/src/test/php.spec.ts +++ b/packages/php-wasm/node/src/test/php.spec.ts @@ -80,7 +80,9 @@ destructive results fifty years hence. He was, I believe, not in the least an ill-natured man: very much the opposite, I should say; but he would not suffer fools gladly.`; -describe.each(SupportedPHPVersions)('PHP %s', (phpVersion) => { +const phpVersions = + 'PHP' in process.env ? [process.env['PHP']!] : SupportedPHPVersions; +describe.each(phpVersions)('PHP %s', (phpVersion) => { let php: PHP; beforeEach(async () => { php = new PHP(await loadNodeRuntime(phpVersion as any)); @@ -867,6 +869,142 @@ describe.each(SupportedPHPVersions)('PHP %s', (phpVersion) => { expect(result.text).toEqual('Hello World!\nHello again!'); }); + it('Non-blocking file descriptors do not wait for asynchronous data', async () => { + const handler = createSpawnHandler( + (command: string[], processApi: any) => { + processApi.flushStdin(); + // Send initial data immediately + processApi.stdout( + new TextEncoder().encode('Initial data\n') + ); + + // Send more data after a delay + setTimeout(() => { + processApi.stdout( + new TextEncoder().encode('Delayed data\n') + ); + processApi.exit(0); + }, 500); + } + ); + + php.setSpawnHandler(handler); + + const result = await php.run({ + code: ` array("pipe","w") + ); + $proc = proc_open( "fetch", $descriptorspec, $pipes ); + + // Make the stream non-blocking + stream_set_blocking($pipes[1], false); + + // First read should get initial data immediately + $data1 = fread($pipes[1], 1024); + echo "First read: " . json_encode($data1) . "\\n"; + + // Second read should return empty string immediately (non-blocking) + $data2 = fread($pipes[1], 1024); + echo "Second read (immediate): " . json_encode($data2) . "\\n"; + + // Wait a bit and try again - should get the delayed data + usleep(600000); // 600ms + $data3 = fread($pipes[1], 1024); + echo "Third read (after delay): " . json_encode($data3) . "\\n"; + + // Fourth read should be empty again + $data4 = fread($pipes[1], 1024); + echo "Fourth read: " . json_encode($data4) . "\\n"; + + proc_close( $proc ); + `, + }); + + expect(result.text).toEqual( + [ + `First read: "Initial data\\n"`, + `Second read (immediate): ""`, + `Third read (after delay): "Delayed data\\n"`, + `Fourth read: ""`, + '', + ].join('\n') + ); + }); + + it('Can poll non-blocking streams until data arrives', async () => { + const handler = createSpawnHandler( + (command: string[], processApi: any) => { + processApi.flushStdin(); + + // Send data in chunks with delays + setTimeout(() => { + processApi.stdout( + new TextEncoder().encode('Chunk 1\n') + ); + }, 200); + + setTimeout(() => { + processApi.stdout( + new TextEncoder().encode('Chunk 2\n') + ); + }, 400); + + setTimeout(() => { + processApi.exit(0); + }, 600); + } + ); + + php.setSpawnHandler(handler); + + const result = await php.run({ + code: ` array("pipe","w") + ); + $proc = proc_open( "fetch", $descriptorspec, $pipes ); + + // Make the stream non-blocking + stream_set_blocking($pipes[1], false); + + $chunks = array(); + $attempts = 0; + $maxAttempts = 20; + + // Poll until we get all data or reach max attempts + while ($attempts < $maxAttempts && !feof($pipes[1])) { + $data = fread($pipes[1], 1024); + if ($data !== "") { + $chunks[] = $data; + echo "Got chunk: " . json_encode($data) . "\\n"; + } else { + echo "No data available, attempt " . ($attempts + 1) . "\\n"; + } + $attempts++; + usleep(100000); // 100ms between attempts + } + + echo "Total chunks received: " . count($chunks) . "\\n"; + echo "Combined data: " . json_encode(implode("", $chunks)) . "\\n"; + + proc_close( $proc ); + `, + }); + + // The exact output may vary due to timing, but we should see: + // - Multiple "No data available" messages + // - Two chunks of data received + // - Total of 2 chunks + expect(result.text).toContain('Got chunk: "Chunk 1\\n"'); + expect(result.text).toContain('Got chunk: "Chunk 2\\n"'); + expect(result.text).toContain('Total chunks received: 2'); + expect(result.text).toContain( + 'Combined data: "Chunk 1\\nChunk 2\\n"' + ); + expect(result.text).toContain('No data available'); + }); + it('feof() returns true when exhausted the synchronous data', async () => { const handler = createSpawnHandler( (command: string[], processApi: any) => { diff --git a/packages/php-wasm/universal/src/lib/is-exit-code.ts b/packages/php-wasm/universal/src/lib/is-exit-code.ts index 831a049921..fad82d7dcc 100644 --- a/packages/php-wasm/universal/src/lib/is-exit-code.ts +++ b/packages/php-wasm/universal/src/lib/is-exit-code.ts @@ -4,9 +4,9 @@ * @param e The error to check * @returns True if the error appears to represent an exit code or status */ -export function isExitCode(e: any): e is { exitCode: number } { +export function isExitCode(e: any): e is { status: number } { if (!(e instanceof Error)) { return false; } - return 'exitCode' in e || (e?.name === 'ExitStatus' && 'status' in e); + return e?.name === 'ExitStatus' && 'status' in e; } diff --git a/packages/php-wasm/universal/src/lib/php.ts b/packages/php-wasm/universal/src/lib/php.ts index b94e1f339f..e64f0384cd 100644 --- a/packages/php-wasm/universal/src/lib/php.ts +++ b/packages/php-wasm/universal/src/lib/php.ts @@ -913,11 +913,13 @@ export class PHP implements Disposable { */ const exit = await Promise.race([ executionFn(), - new Promise((_, reject) => { + new Promise((resolve, reject) => { errorListener = (e: ErrorEvent) => { - logger.error(e); - logger.error(e.error); - if (!isExitCode(e.error)) { + if (isExitCode(e.error) && e.error.status === 0) { + resolve(e.error.status); + } else { + logger.error(e); + logger.error(e.error); const rethrown = new Error('Rethrown'); rethrown.cause = e.error; (rethrown as any).betterMessage = e.message; @@ -938,7 +940,7 @@ export class PHP implements Disposable { * turn exit code errors into integers again. */ if (isExitCode(e)) { - return e.exitCode; + return e.status; } stdout.controller.error(e); diff --git a/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts b/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts index 9093c03ce1..da7e2e3ba3 100644 --- a/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts +++ b/packages/php-wasm/universal/src/lib/wasm-error-reporting.ts @@ -80,7 +80,7 @@ export function improveWASMErrorReporting(runtime: Runtime) { throw e; } - if (!isExitCode(e) || e.exitCode !== 0) { + if (!isExitCode(e) || e.status !== 0) { showCriticalErrorBox(clearMessage); } throw e;