/*
* Copyright 2025 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.
*/
// Duplicate of test_select_blocking.c using poll() instead of select()
#include <poll.h>
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/time.h>
#define TIMEOUT_MS 300
// It is possible for the node timers (such as setTimeout or Atomics.wait) to wake up
// slightly earlier than requested. Because we measure times accurately using
// clock_gettime, we give tests a 20 milliseconds error margin to avoid flaky timeouts.
#define TIMEOUT_MARGIN_MS 20
void sleep_ms(int ms) {
usleep(ms * 1000);
}
int64_t timespec_delta_ms(struct timespec* begin, struct timespec* end) {
int64_t delta_sec = end->tv_sec - begin->tv_sec;
int64_t delta_nsec = end->tv_nsec - begin->tv_nsec;
assert(delta_sec >= 0);
assert(delta_nsec > -1000000000 && delta_nsec < 1000000000);
int64_t delta_ms = (delta_sec * 1000000000LL + delta_nsec) / 1000000;
assert(delta_ms >= 0);
return delta_ms;
}
// Check if timeout works without fds
void test_timeout_without_fds() {
printf("test_timeout_without_fds\n");
struct timespec begin, end;
clock_gettime(CLOCK_MONOTONIC, &begin);
assert(poll(NULL, 0, TIMEOUT_MS) == 0);
clock_gettime(CLOCK_MONOTONIC, &end);
int64_t duration = timespec_delta_ms(&begin, &end);
printf(" -> duration: %lld ms\n", duration);
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
}
// Check if timeout works with fds without events
void test_timeout_with_fds_without_events() {
printf("test_timeout_with_fds_without_events\n");
struct timespec begin, end;
int pipe_a[2];
assert(pipe(pipe_a) == 0);
clock_gettime(CLOCK_MONOTONIC, &begin);
struct pollfd fds = {pipe_a[0], 0, 0};
assert(poll(&fds, 1, TIMEOUT_MS) == 0);
clock_gettime(CLOCK_MONOTONIC, &end);
int64_t duration = timespec_delta_ms(&begin, &end);
printf(" -> duration: %lld ms\n", duration);
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
close(pipe_a[0]); close(pipe_a[1]);
}
int pipe_shared[2];
void *write_after_sleep(void * arg) {
const char *t = "test\n";
sleep_ms(TIMEOUT_MS);
write(pipe_shared[1], t, strlen(t));
return NULL;
}
// Check if poll can unblock on an event
void test_unblock_poll() {
printf("test_unblock_poll\n");
struct timespec begin, end;
pthread_t tid;
int pipe_a[2];
assert(pipe(pipe_a) == 0);
assert(pipe(pipe_shared) == 0);
struct pollfd fds[2] = {
{pipe_a[0], POLLIN, 0},
{pipe_shared[0], POLLIN, 0},
};
clock_gettime(CLOCK_MONOTONIC, &begin);
assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0);
assert(poll(fds, 2, -1) == 1);
clock_gettime(CLOCK_MONOTONIC, &end);
assert(fds[1].revents & POLLIN);
int64_t duration = timespec_delta_ms(&begin, &end);
printf(" -> duration: %lld ms\n", duration);
assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS);
pthread_join(tid, NULL);
close(pipe_a[0]); close(pipe_a[1]);
close(pipe_shared[0]); close(pipe_shared[1]);
}
int threads_running = 0;
pthread_mutex_t running_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t running_cv = PTHREAD_COND_INITIALIZER;
void *do_poll_in_thread(void * arg) {
struct timespec begin, end;
clock_gettime(CLOCK_MONOTONIC, &begin);
struct pollfd fds = {pipe_shared[0], POLLIN, 0};
pthread_mutex_lock(&running_lock);
threads_running++;
pthread_cond_signal(&running_cv);
pthread_mutex_unlock(&running_lock);
assert(poll(&fds, 1, 4000) == 1);
clock_gettime(CLOCK_MONOTONIC, &end);
assert(fds.revents & POLLIN);
int64_t duration = timespec_delta_ms(&begin, &end);
printf(" -> duration: %lld ms\n", duration);
assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000));
return NULL;
}
// Check if poll works in threads
void test_poll_in_threads() {
printf("test_poll_in_threads\n");
pthread_t tid1, tid2;
const char *t = "test\n";
assert(pipe(pipe_shared) == 0);
assert(pthread_create(&tid1, NULL, do_poll_in_thread, NULL) == 0);
assert(pthread_create(&tid2, NULL, do_poll_in_thread, NULL) == 0);
pthread_mutex_lock(&running_lock);
while (threads_running != 2) {
pthread_cond_wait(&running_cv, &running_lock);
}
pthread_mutex_unlock(&running_lock);
sleep_ms(2 * TIMEOUT_MS);
write(pipe_shared[1], t, strlen(t));
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(pipe_shared[0]); close(pipe_shared[1]);
}
// Check if poll works with ready fds
void test_ready_fds() {
printf("test_ready_fds\n");
struct timeval tv;
fd_set readfds;
const char *t = "test\n";
int pipe_c[2];
int pipe_d[2];
assert(pipe(pipe_c) == 0);
assert(pipe(pipe_d) == 0);
write(pipe_c[1], t, strlen(t));
write(pipe_d[1], t, strlen(t));
struct pollfd fds[2] = {
{pipe_c[0], POLLIN, 0},
{pipe_d[0], POLLIN, 0},
};
assert(poll(fds, 2, 0) == 2);
assert(fds[0].revents & POLLIN);
assert(fds[1].revents & POLLIN);
fds[0].revents = 0;
fds[1].revents = 0;
assert(poll(fds, 2, 0) == 2);
assert(fds[0].revents & POLLIN);
assert(fds[1].revents & POLLIN);
close(pipe_c[0]); close(pipe_c[1]);
close(pipe_d[0]); close(pipe_d[1]);
}
int main() {
test_poll_in_threads();
test_timeout_without_fds();
test_timeout_with_fds_without_events();
test_unblock_poll();
test_ready_fds();
printf("done\n");
return 0;
}