hostx: the X server's poll reports POLLOUT from the client socket's room, and counts it

gex_poll set POLLOUT on every descriptor but returned only for POLLIN, so
when a reply did not fit a guest's kernel socket (256 KiB) the server
waited for input that never came: the client was waiting for the reply.
xbig in the page hung on its second 300 KB property read (xb3/xb4/xb5),
with and without the function JIT. hostx.js gains writable(fd)
(k_sock_writable for kernel sockets; the compositor's queues never fill).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-08 23:34:59 -04:00
1 parent 48696024a6
commit 895a3b8ca1
2 files changed
+23 -7

No files matched your search

+10 -1
View File
@@ -419,6 +419,15 @@ export class HostX {
}
},
/** Room to write: a guest's socket in the kernel fills (256 KiB); the
* compositor's queues never do. */
writable(fd) {
const ep = self.#fds.get(fd);
if (!ep) return -1;
if (ep.kind === 'kernel') return self.#k.k_sock_writable(ep.id) ? 1 : 0;
return 1;
},
send(fd, buf, len, fds, nfds) {
const ep = self.#fds.get(fd);
if (!ep) return -EBADF;
@@ -515,7 +524,7 @@ export class HostX {
const wrapped = {};
let left = this.#trace;
for (const [name, fn] of Object.entries(raw)) {
if (name === 'tick' || name === 'wait' || name === 'readable') {
if (name === 'tick' || name === 'wait' || name === 'readable' || name === 'writable') {
wrapped[name] = fn;
continue;
}
+13 -6
View File
@@ -36,6 +36,7 @@ EM_JS(int, gexjs_connect, (int fd, const char *path, int len), { return Module.g
EM_JS(int, gexjs_accept, (int fd), { return Module.gexos.accept(fd); });
EM_JS(int, gexjs_close, (int fd), { return Module.gexos.close(fd); });
EM_JS(int, gexjs_readable, (int fd), { return Module.gexos.readable(fd); });
EM_JS(int, gexjs_writable, (int fd), { return Module.gexos.writable(fd); });
EM_JS(int, gexjs_send, (int fd, const char *buf, int len, const int *fds, int nfds),
{ return Module.gexos.send(fd, buf, len, fds, nfds); });
EM_JS(int, gexjs_recv, (int fd, char *buf, int len, int *fds, int maxfds, int *nfds),
@@ -510,7 +511,7 @@ gex_poll(struct pollfd *fds, nfds_t nfds, int timeout)
fds[i].revents = 0;
if (fds[i].fd < 0)
continue;
if (fds[i].events & POLLOUT)
if ((fds[i].events & POLLOUT) && gexjs_writable(fds[i].fd) > 0)
fds[i].revents |= POLLOUT;
if (fds[i].events & POLLIN) {
int r = gexjs_readable(fds[i].fd);
@@ -519,11 +520,14 @@ gex_poll(struct pollfd *fds, nfds_t nfds, int timeout)
else if (r < 0)
fds[i].revents |= POLLNVAL;
}
if (fds[i].revents & (POLLIN | POLLNVAL))
if (fds[i].revents)
ready++;
}
/* POLLOUT alone is not worth waking for: the server asks for it while
* it has a client's reply buffered, and a queue here is never full. */
/* POLLOUT counts. The server asks for it when a client's socket in
* the kernel is full (256 KiB) with a reply still buffered, and it
* writes the rest only when poll says so: a poll that kept waiting
* for POLLIN instead hung every reply bigger than the socket (the
* client waits for the reply and sends nothing). */
if (ready || timeout == 0)
return ready;
int slice = timeout < 0 ? 20 : (timeout - (int) deadline_slept > 20
@@ -539,10 +543,13 @@ gex_poll(struct pollfd *fds, nfds_t nfds, int timeout)
for (nfds_t i = 0; i < nfds; i++) {
fds[i].revents = 0;
if (fds[i].fd >= 0 && (fds[i].events & POLLIN) &&
gexjs_readable(fds[i].fd) > 0) {
gexjs_readable(fds[i].fd) > 0)
fds[i].revents |= POLLIN;
if (fds[i].fd >= 0 && (fds[i].events & POLLOUT) &&
gexjs_writable(fds[i].fd) > 0)
fds[i].revents |= POLLOUT;
if (fds[i].revents)
last++;
}
}
return last;
}