blob: 848799900846385a65b67369624346c0d1050fc9 [file]
/*
* Copyright 2026 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*
* A *blocking* epoll_wait() on a real socket is woken by a datagram that arrives
* *after* the wait has already blocked. The datagram is sent on a delay (from
* another thread under -pthread, or a timer under JSPI) so epoll_wait() must
* suspend - the proxied worker under PROXY_TO_PTHREAD, or the calling stack
* under JSPI - and be woken through the unified readiness wait-queue, the same
* SOCKFS.emit bridge poll() rides.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <emscripten.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef __EMSCRIPTEN_PTHREADS__
#include <pthread.h>
#endif
int rx = -1, tx = -1;
struct sockaddr_in addr;
void send_ping(void* arg) {
assert(sendto(tx, "ping", 4, 0, (struct sockaddr*)&addr, sizeof(addr)) == 4);
}
#ifdef __EMSCRIPTEN_PTHREADS__
void* sender(void* arg) {
usleep(100000); // let epoll_wait() block first
send_ping(NULL);
return NULL;
}
#endif
int main(void) {
rx = socket(AF_INET, SOCK_DGRAM, 0);
tx = socket(AF_INET, SOCK_DGRAM, 0);
assert(rx >= 0 && tx >= 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
assert(bind(rx, (struct sockaddr*)&addr, sizeof(addr)) == 0);
socklen_t l = sizeof(addr);
assert(getsockname(rx, (struct sockaddr*)&addr, &l) == 0);
int ep = epoll_create1(0);
assert(ep >= 0);
struct epoll_event ev = { .events = EPOLLIN };
ev.data.fd = rx;
assert(epoll_ctl(ep, EPOLL_CTL_ADD, rx, &ev) == 0);
// Arrange the datagram to arrive only after epoll_wait() is already blocking,
// so it can only complete by being woken - not by the initial derivation.
#ifdef __EMSCRIPTEN_PTHREADS__
// Under PROXY_TO_PTHREAD main() runs on a worker that parks in epoll_wait, so
// its event loop can't fire an emscripten_async_call timer - the wake is a
// cross-thread memory notify. Send from a separate thread instead.
pthread_t t;
assert(pthread_create(&t, NULL, sender, NULL) == 0);
#else
emscripten_async_call(send_ping, NULL, 100);
#endif
struct epoll_event out[4];
int n = epoll_wait(ep, out, 4, -1); // blocks; only the arrival can wake it
assert(n == 1);
assert(out[0].events & EPOLLIN);
assert(out[0].data.fd == rx);
char buf[4];
assert(recv(rx, buf, sizeof(buf), 0) == 4);
assert(memcmp(buf, "ping", 4) == 0);
close(ep);
close(rx);
close(tx);
printf("done\n");
return 0;
}