Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4c5aa92

Browse files
committed
PHP: Fix deadlock in poll(2)
We patch poll(2) with a JavaScript implementation. This PR resolves two problems in it: ### Infinite wait on poll(socketd, POLLIN) Polling a socket for more data with `poll(socketd, POLLIN)` used to hang once the data was exhausted and the socket closed because we never notified the caller about POLLHUP. We even have a related workaround in wordpress-components: ```php $bytes = fread( $this->file_pointer, $n ); /** * Workaround for a streaming bug in WordPress Playground. * * Without the feof() call, Playground doesn't notice when the stream reaches EOF. * The feof() call in internal_reached_end_of_data() somehow does not trigger the * EOF event. It must be here, right after fread(). * * @todo: Improve the streaming support in WordPress Playground. */ feof( $this->file_pointer ); if ( false === $bytes ) { throw new ByteStreamException( 'Failed to read from file' ); } ``` This PR makes poll(socketd, POLLIN) aware of a POLLHUP event. I'm not sure if that's POSIX-correct and we should verify that before merging, but it actually solves the issue. CC @brandonpayton ### Not recognizing a socketd as socket PHP.wasm supports child processes and keeps track of them in PHPWASM.child_proc_by_fd. However, we never clean up the socketd => procInfo mapping. When a socketd number is getting reused, PHP would mistakenly identify it as a child process and never poll the actual socket for data. This PR works around it by testing for socket-ness first and for child-process-ness second. This is ultimatly a hack. In a follow up PR we should find a way to actually remove closed socketd from the child process map. ## Remaining work * Test coverage * Start an issue to actually cleanup child process ids
1 parent fb0ffb5 commit 4c5aa92

File tree

23 files changed

+8436
-9437
lines changed

23 files changed

+8436
-9437
lines changed

‎packages/php-wasm/compile/php/php_wasm.c‎

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,12 @@ EM_JS(int, wasm_poll_socket, (php_socket_t socketd, int events, int timeout), {
147147

148148
return returnCallback((wakeUp) => {
149149
const polls = [];
150-
if (socketd in PHPWASM.child_proc_by_fd) {
151-
// This is a child process-related socket.
152-
const procInfo = PHPWASM.child_proc_by_fd[socketd];
153-
if (procInfo.exited) {
154-
wakeUp(0);
155-
return;
156-
}
157-
polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
158-
} else if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
150+
/**
151+
* Check for socket-ness first. We don't clean up child_proc_by_fd yet and
152+
* sometimes get duplicate entries. isSocket is more reliable out of the two –
153+
* let's check for it first.
154+
*/
155+
if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
159156
// This is, most likely, a websocket. Let's make sure.
160157
const sock = getSocketFromFD(socketd);
161158
if (!sock) {
@@ -184,23 +181,38 @@ EM_JS(int, wasm_poll_socket, (php_socket_t socketd, int events, int timeout), {
184181
return;
185182
}
186183
for (const ws of webSockets) {
187-
if (events & POLLIN || events & POLLPRI) {
188-
polls.push(PHPWASM.awaitData(ws));
189-
lookingFor.add('POLLIN');
190-
}
191-
if (events & POLLOUT) {
192-
polls.push(PHPWASM.awaitConnection(ws));
193-
lookingFor.add('POLLOUT');
194-
}
195-
if (events & POLLHUP) {
196-
polls.push(PHPWASM.awaitClose(ws));
197-
lookingFor.add('POLLHUP');
198-
}
199-
if (events & POLLERR || events & POLLNVAL) {
200-
polls.push(PHPWASM.awaitError(ws));
201-
lookingFor.add('POLLERR');
202-
}
184+
if (events & POLLIN || events & POLLPRI) {
185+
polls.push(PHPWASM.awaitData(ws));
186+
lookingFor.add('POLLIN');
187+
}
188+
if (events & POLLOUT) {
189+
polls.push(PHPWASM.awaitConnection(ws));
190+
lookingFor.add('POLLOUT');
191+
}
192+
// Notify the user the socket is now closed even if the only requested
193+
// in or out events.
194+
if (
195+
events & POLLHUP ||
196+
events & POLLIN ||
197+
events & POLLOUT ||
198+
events & POLLERR
199+
) {
200+
polls.push(PHPWASM.awaitClose(ws));
201+
lookingFor.add('POLLHUP');
202+
}
203+
if (events & POLLERR || events & POLLNVAL) {
204+
polls.push(PHPWASM.awaitError(ws));
205+
lookingFor.add('POLLERR');
206+
}
203207
}
208+
} else if (socketd in PHPWASM.child_proc_by_fd) {
209+
// This is a child process-related socket.
210+
const procInfo = PHPWASM.child_proc_by_fd[socketd];
211+
if (procInfo.exited) {
212+
wakeUp(0);
213+
return;
214+
}
215+
polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
204216
} else {
205217
setTimeout(function () {
206218
wakeUp(1);

‎packages/php-wasm/compile/php/phpwasm-emscripten-library.js‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ const LibraryExample = {
6666
}
6767
}
6868
}
69-
};
69+
};
70+
71+
// Clean up the fd -> childProcess mapping when the fd is closed:
72+
const originalClose = FS.close;
73+
FS.close = function (stream) {
74+
originalClose(stream);
75+
delete PHPWASM.child_proc_by_fd[stream.fd];
76+
};
77+
7078
PHPWASM.child_proc_by_fd = {};
7179
PHPWASM.child_proc_by_pid = {};
7280
PHPWASM.input_devices = {};
@@ -440,6 +448,21 @@ const LibraryExample = {
440448
PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo;
441449

442450
cp.on('exit', function (code) {
451+
for (const fd of [
452+
// The child process exited. Let's clean up its output streams:
453+
ProcInfo.stdoutChildFd,
454+
ProcInfo.stderrChildFd,
455+
// Note we're not closing stdinFd as the parent still might be holding on to it.
456+
457+
// We won't close these because the parent process is responsible for that:
458+
// ProcInfo.stdoutParentFd,
459+
// ProcInfo.stderrParentFd,
460+
]) {
461+
if(FS.streams[fd] && !FS.isClosed(FS.streams[fd])) {
462+
FS.close(FS.streams[fd]);
463+
}
464+
}
465+
443466
ProcInfo.exitCode = code;
444467
ProcInfo.exited = true;
445468
// Emit events for the wasm_poll_socket function.
0 Bytes
Binary file not shown.

‎packages/php-wasm/node/asyncify/php_7_2.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8326,7 +8326,7 @@ export function init(RuntimeName, PHPLoader) {
83268326
polls.push(PHPWASM.awaitConnection(ws));
83278327
lookingFor.add('POLLOUT');
83288328
}
8329-
if (events & POLLHUP) {
8329+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83308330
polls.push(PHPWASM.awaitClose(ws));
83318331
lookingFor.add('POLLHUP');
83328332
}

‎packages/php-wasm/node/asyncify/php_7_3.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8326,7 +8326,7 @@ export function init(RuntimeName, PHPLoader) {
83268326
polls.push(PHPWASM.awaitConnection(ws));
83278327
lookingFor.add('POLLOUT');
83288328
}
8329-
if (events & POLLHUP) {
8329+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83308330
polls.push(PHPWASM.awaitClose(ws));
83318331
lookingFor.add('POLLHUP');
83328332
}

‎packages/php-wasm/node/asyncify/php_7_4.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8330,7 +8330,7 @@ export function init(RuntimeName, PHPLoader) {
83308330
polls.push(PHPWASM.awaitConnection(ws));
83318331
lookingFor.add('POLLOUT');
83328332
}
8333-
if (events & POLLHUP) {
8333+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83348334
polls.push(PHPWASM.awaitClose(ws));
83358335
lookingFor.add('POLLHUP');
83368336
}

‎packages/php-wasm/node/asyncify/php_8_0.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8330,7 +8330,7 @@ export function init(RuntimeName, PHPLoader) {
83308330
polls.push(PHPWASM.awaitConnection(ws));
83318331
lookingFor.add('POLLOUT');
83328332
}
8333-
if (events & POLLHUP) {
8333+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83348334
polls.push(PHPWASM.awaitClose(ws));
83358335
lookingFor.add('POLLHUP');
83368336
}

‎packages/php-wasm/node/asyncify/php_8_1.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8362,7 +8362,7 @@ export function init(RuntimeName, PHPLoader) {
83628362
polls.push(PHPWASM.awaitConnection(ws));
83638363
lookingFor.add('POLLOUT');
83648364
}
8365-
if (events & POLLHUP) {
8365+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83668366
polls.push(PHPWASM.awaitClose(ws));
83678367
lookingFor.add('POLLHUP');
83688368
}

‎packages/php-wasm/node/asyncify/php_8_2.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8364,7 +8364,7 @@ export function init(RuntimeName, PHPLoader) {
83648364
polls.push(PHPWASM.awaitConnection(ws));
83658365
lookingFor.add('POLLOUT');
83668366
}
8367-
if (events & POLLHUP) {
8367+
if (events & POLLHUP||events&POLLIN||events&POLLOUT||events&POLLPRI) {
83688368
polls.push(PHPWASM.awaitClose(ws));
83698369
lookingFor.add('POLLHUP');
83708370
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /