From 9200517bd6f28522590e7bdb9279e84b00350f8d Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Thu, 5 Mar 2026 01:41:19 +0100 Subject: [PATCH 1/2] [test] Convert select and poll tests to using `clock_gettime` (#26280) Prefer `clock_gettime` with `CLOCK_MONOTONIC` instead of `gettimeofday` whose implementation isn't guaranteed to be monotonic. --- test/core/test_poll_blocking.c | 47 +++++++++++++---------- test/core/test_poll_blocking_asyncify.c | 33 +++++++++------- test/core/test_select_blocking.c | 51 ++++++++++++++----------- test/test_core.py | 3 -- 4 files changed, 75 insertions(+), 59 deletions(-) diff --git a/test/core/test_poll_blocking.c b/test/core/test_poll_blocking.c index bdbd6b1ee1571..959722866ba7d 100644 --- a/test/core/test_poll_blocking.c +++ b/test/core/test_poll_blocking.c @@ -22,23 +22,28 @@ void sleep_ms(int ms) { usleep(ms * 1000); } -int64_t timeval_delta_ms(struct timeval* begin, struct timeval* end) { - int64_t delta_s = end->tv_sec - begin->tv_sec; - int64_t delta_us = end->tv_usec - begin->tv_usec; - assert(delta_s >= 0); - return (delta_s * 1000) + (delta_us / 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 * 1000) + (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 timeval begin, end; + struct timespec begin, end; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(poll(NULL, 0, TIMEOUT_MS) == 0); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); } @@ -46,17 +51,17 @@ void test_timeout_without_fds() { // Check if timeout works with fds without events void test_timeout_with_fds_without_events() { printf("test_timeout_with_fds_without_events\n"); - struct timeval begin, end; + struct timespec begin, end; int pipe_a[2]; assert(pipe(pipe_a) == 0); - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); struct pollfd fds = {pipe_a[0], 0, 0}; assert(poll(&fds, 1, TIMEOUT_MS) == 0); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); @@ -77,7 +82,7 @@ void *write_after_sleep(void * arg) { // Check if poll can unblock on an event void test_unblock_poll() { printf("test_unblock_poll\n"); - struct timeval begin, end; + struct timespec begin, end; pthread_t tid; int pipe_a[2]; @@ -88,13 +93,13 @@ void test_unblock_poll() { {pipe_a[0], POLLIN, 0}, {pipe_shared[0], POLLIN, 0}, }; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0); assert(poll(fds, 2, -1) == 1); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); assert(fds[1].revents & POLLIN); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); @@ -105,15 +110,15 @@ void test_unblock_poll() { } void *do_poll_in_thread(void * arg) { - struct timeval begin, end; + struct timespec begin, end; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); struct pollfd fds = {pipe_shared[0], POLLIN, 0}; assert(poll(&fds, 1, 4000) == 1); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); assert(fds.revents & POLLIN); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert((duration >= TIMEOUT_MS) && (duration < 4000)); diff --git a/test/core/test_poll_blocking_asyncify.c b/test/core/test_poll_blocking_asyncify.c index b4d9bf6c735a5..444a8038c0e53 100644 --- a/test/core/test_poll_blocking_asyncify.c +++ b/test/core/test_poll_blocking_asyncify.c @@ -19,23 +19,29 @@ #include -int64_t timeval_delta_ms(struct timeval* begin, struct timeval* end) { - int64_t delta_s = end->tv_sec - begin->tv_sec; - int64_t delta_us = end->tv_usec - begin->tv_usec; - assert(delta_s >= 0); - return (delta_s * 1000) + (delta_us / 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 * 1000) + (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 timeval begin, end; + struct timespec begin = {0}; + struct timespec end = {0}; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(poll(NULL, 0, 1000) == 0); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= 1000); } @@ -51,7 +57,8 @@ void write_to_pipe(void * arg) { // Check if poll can unblock on an event void test_unblock_poll() { printf("test_unblock_poll\n"); - struct timeval begin, end; + struct timespec begin = {0}; + struct timespec end = {0}; int pipe_a[2]; assert(pipe(pipe_a) == 0); @@ -62,12 +69,12 @@ void test_unblock_poll() { {pipe_shared[0], POLLIN, 0}, }; emscripten_set_timeout(write_to_pipe, 1000, NULL); - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(poll(fds, 2, -1) == 1); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); assert(fds[1].revents & POLLIN); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= 1000); diff --git a/test/core/test_select_blocking.c b/test/core/test_select_blocking.c index ddedab1aee851..8b68e9231e502 100644 --- a/test/core/test_select_blocking.c +++ b/test/core/test_select_blocking.c @@ -19,25 +19,31 @@ void sleep_ms(int ms) { usleep(ms * 1000); } -int64_t timeval_delta_ms(struct timeval* begin, struct timeval* end) { - int64_t delta_s = end->tv_sec - begin->tv_sec; - int64_t delta_us = end->tv_usec - begin->tv_usec; - assert(delta_s >= 0); - return (delta_s * 1000) + (delta_us / 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 * 1000) + (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 timeval tv, begin, end; + struct timespec begin, end; + struct timeval tv; tv.tv_sec = 0; tv.tv_usec = TIMEOUT_MS * 1000; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(select(0, NULL, NULL, NULL, &tv) == 0); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); } @@ -45,7 +51,8 @@ void test_timeout_without_fds() { // Check if timeout works with fds without events void test_timeout_with_fds_without_events() { printf("test_timeout_with_fds_without_events\n"); - struct timeval tv, begin, end; + struct timespec begin, end; + struct timeval tv; fd_set readfds; int pipe_a[2]; @@ -55,11 +62,11 @@ void test_timeout_with_fds_without_events() { tv.tv_usec = TIMEOUT_MS * 1000; FD_ZERO(&readfds); FD_SET(pipe_a[0], &readfds); - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(select(pipe_a[0] + 1, &readfds, NULL, NULL, &tv) == 0); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); @@ -80,7 +87,7 @@ void *write_after_sleep(void * arg) { // Check if select can unblock on an event void test_unblock_select() { printf("test_unblock_select\n"); - struct timeval begin, end; + struct timespec begin, end; fd_set readfds; pthread_t tid; int pipe_a[2]; @@ -92,13 +99,13 @@ void test_unblock_select() { FD_SET(pipe_a[0], &readfds); FD_SET(pipe_shared[0], &readfds); int maxfd = (pipe_a[0] > pipe_shared[0] ? pipe_a[0] : pipe_shared[0]); - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(pthread_create(&tid, NULL, write_after_sleep, NULL) == 0); assert(select(maxfd + 1, &readfds, NULL, NULL, NULL) == 1); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); assert(FD_ISSET(pipe_shared[0], &readfds)); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert(duration >= TIMEOUT_MS); @@ -109,9 +116,9 @@ void test_unblock_select() { } void *do_select_in_thread(void * arg) { - struct timeval begin, end; - fd_set readfds; + struct timespec begin, end; struct timeval tv; + fd_set readfds; tv.tv_sec = 4; tv.tv_usec = 0; @@ -119,12 +126,12 @@ void *do_select_in_thread(void * arg) { FD_SET(pipe_shared[0], &readfds); int maxfd = pipe_shared[0]; - gettimeofday(&begin, NULL); + clock_gettime(CLOCK_MONOTONIC, &begin); assert(select(maxfd + 1, &readfds, NULL, NULL, &tv) == 1); - gettimeofday(&end, NULL); + clock_gettime(CLOCK_MONOTONIC, &end); assert(FD_ISSET(pipe_shared[0], &readfds)); - int64_t duration = timeval_delta_ms(&begin, &end); + int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); assert((duration >= TIMEOUT_MS) && (duration < 4000)); diff --git a/test/test_core.py b/test/test_core.py index a55bf02c9b970..b0a64a4940c7c 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9573,17 +9573,14 @@ def test_syscall_intercept(self): self.do_core_test('test_syscall_intercept.c') @requires_pthreads - @flaky('https://github.com/emscripten-core/emscripten/issues/26256') def test_select_blocking(self): self.do_runf('core/test_select_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) @requires_pthreads - @flaky('https://github.com/emscripten-core/emscripten/issues/26256') def test_poll_blocking(self): self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) @with_asyncify_and_jspi - @flaky('https://github.com/emscripten-core/emscripten/issues/26256') def test_poll_blocking_asyncify(self): if self.get_setting('JSPI') and engine_is_v8(self.get_current_js_engine()): self.skipTest('test requires setTimeout which is not supported under v8') From a3c4f9d45bc8c9a1712c5f5d9f65a6d888ffa6a9 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 6 Mar 2026 17:39:57 +0000 Subject: [PATCH 2/2] Add timeout margin to tests. --- test/core/test_poll_blocking.c | 12 ++++++++---- test/core/test_poll_blocking_asyncify.c | 10 +++++++--- test/core/test_select_blocking.c | 12 ++++++++---- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/test/core/test_poll_blocking.c b/test/core/test_poll_blocking.c index 959722866ba7d..ef6cbf9bf374d 100644 --- a/test/core/test_poll_blocking.c +++ b/test/core/test_poll_blocking.c @@ -17,6 +17,10 @@ #include #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 5 milliseconds error margin to avoid flaky timeouts. +#define TIMEOUT_MARGIN_MS 5 void sleep_ms(int ms) { usleep(ms * 1000); @@ -45,7 +49,7 @@ void test_timeout_without_fds() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); } // Check if timeout works with fds without events @@ -63,7 +67,7 @@ void test_timeout_with_fds_without_events() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); close(pipe_a[0]); close(pipe_a[1]); } @@ -101,7 +105,7 @@ void test_unblock_poll() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); pthread_join(tid, NULL); @@ -120,7 +124,7 @@ void *do_poll_in_thread(void * arg) { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert((duration >= TIMEOUT_MS) && (duration < 4000)); + assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000)); return NULL; } diff --git a/test/core/test_poll_blocking_asyncify.c b/test/core/test_poll_blocking_asyncify.c index 444a8038c0e53..a5a32bf71e7b4 100644 --- a/test/core/test_poll_blocking_asyncify.c +++ b/test/core/test_poll_blocking_asyncify.c @@ -16,9 +16,13 @@ #include #include #include - #include +// 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 5 milliseconds error margin to avoid flaky timeouts. +#define TIMEOUT_MARGIN_MS 5 + 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; @@ -43,7 +47,7 @@ void test_timeout_without_fds() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= 1000); + assert(duration >= 1000 - TIMEOUT_MARGIN_MS); } int pipe_shared[2]; @@ -76,7 +80,7 @@ void test_unblock_poll() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= 1000); + assert(duration >= 1000 - TIMEOUT_MARGIN_MS); close(pipe_a[0]); close(pipe_a[1]); close(pipe_shared[0]); close(pipe_shared[1]); diff --git a/test/core/test_select_blocking.c b/test/core/test_select_blocking.c index 8b68e9231e502..4d8105afa3106 100644 --- a/test/core/test_select_blocking.c +++ b/test/core/test_select_blocking.c @@ -14,6 +14,10 @@ #include #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 5 milliseconds error margin to avoid flaky timeouts. +#define TIMEOUT_MARGIN_MS 5 void sleep_ms(int ms) { usleep(ms * 1000); @@ -45,7 +49,7 @@ void test_timeout_without_fds() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); } // Check if timeout works with fds without events @@ -68,7 +72,7 @@ void test_timeout_with_fds_without_events() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); close(pipe_a[0]); close(pipe_a[1]); } @@ -107,7 +111,7 @@ void test_unblock_select() { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert(duration >= TIMEOUT_MS); + assert(duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS); pthread_join(tid, NULL); @@ -133,7 +137,7 @@ void *do_select_in_thread(void * arg) { int64_t duration = timespec_delta_ms(&begin, &end); printf(" -> duration: %lld ms\n", duration); - assert((duration >= TIMEOUT_MS) && (duration < 4000)); + assert((duration >= TIMEOUT_MS - TIMEOUT_MARGIN_MS) && (duration < 4000)); return NULL; }