Skip to content

Commit 01b61e4

Browse files
authored
Remove loop from emscripten_thread_sleep. NFC (#26585)
We have code in the lower level `emscrpeten_futex_wait` that takes care of splitting up the wait for the main runtime thread. For normal pthreads I don't see any need to run the message queue constantly while the thread is sleeping. As part of this change I found that I had to improve the accuracy of the timeslicing `emscripten_futex_wait`. The new code uses an absolute target time, so regardless of how much time it spends between calls to `atomic.wait` it still returns at the correct time. Previously we were assuming that we could call `atomic.wait32` with 1ms timeout N times to achieve Nms of sleeping, but this doesn't always hold, for various reasons. Followup to #26471
1 parent 82e1885 commit 01b61e4

File tree

7 files changed

+51
-67
lines changed

7 files changed

+51
-67
lines changed

system/lib/pthread/emscripten_futex_wait.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,15 @@ int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms)
172172
wakeup_interval = 100 * 1000000;
173173
}
174174

175-
// When wakeup_interval is set, we use remainder_ns to track how many ns
176-
// remain of the intiial max_wait_ns.
177-
int64_t remainder_ns = 0;
175+
// When wakeup_interval is set, we use end_time to track the absolute
176+
// time when the wait should end.
177+
double end_time = 0;
178178
if (wakeup_interval) {
179-
remainder_ns = max_wait_ns;
180-
if (remainder_ns < 0) {
179+
if (max_wait_ms == INFINITY) {
181180
max_wait_ns = wakeup_interval;
182181
} else {
183-
max_wait_ns = MIN(remainder_ns, wakeup_interval);
182+
end_time = emscripten_get_now() + max_wait_ms;
183+
max_wait_ns = MIN(max_wait_ns, wakeup_interval);
184184
}
185185
}
186186

@@ -222,14 +222,12 @@ int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms)
222222
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
223223
return -ECANCELED;
224224
}
225-
// If remainder_ns is negative it means we want wait forever, and we don't
226-
// need to decrement remainder_ns in that case.
227-
if (wakeup_interval && remainder_ns >= 0) {
228-
remainder_ns -= wakeup_interval;
229-
if (remainder_ns <= 0) {
225+
if (wakeup_interval && max_wait_ms != INFINITY) {
226+
double remainder_ms = end_time - emscripten_get_now();
227+
if (remainder_ms <= 0) {
230228
break;
231229
}
232-
max_wait_ns = MIN(remainder_ns, wakeup_interval);
230+
max_wait_ns = MIN((int64_t)(remainder_ms * 1e6), wakeup_interval);
233231
}
234232
} while (wakeup_interval && ret == ATOMICS_WAIT_TIMED_OUT);
235233
#endif

system/lib/pthread/library_pthread.c

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -65,54 +65,32 @@ int sched_get_priority_min(int policy) {
6565
return 0;
6666
}
6767

68-
int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *restrict attr, int *restrict prioceiling)
69-
{
68+
int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *restrict attr, int *restrict prioceiling) {
7069
// Not supported either in Emscripten or musl, return a faked value.
7170
if (prioceiling) *prioceiling = 99;
7271
return 0;
7372
}
7473

75-
int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr, int prioceiling)
76-
{
74+
int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr, int prioceiling) {
7775
// Not supported either in Emscripten or musl, return an error.
7876
return EPERM;
7977
}
8078

81-
static uint32_t dummyZeroAddress = 0;
82-
8379
void emscripten_thread_sleep(double msecs) {
84-
double now = emscripten_get_now();
85-
double target = now + msecs;
86-
87-
// If we have less than this many msecs left to wait, busy spin that instead.
88-
double min_ms_slice_to_sleep = 0.1;
89-
90-
// Break up sleeping so that we process proxied work at regular intervals.
91-
// TODO(sbc): This should be removed and/or moved down into
92-
// `emscripten_futex_wait`.
93-
double max_ms_slice_to_sleep = 100;
94-
95-
emscripten_conditional_set_current_thread_status(
96-
EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_SLEEPING);
97-
98-
do {
99-
// Keep processing the main loop of the calling thread.
100-
__pthread_testcancel(); // pthreads spec: sleep is a cancellation point, so must test if this
101-
// thread is cancelled during the sleep.
102-
emscripten_current_thread_process_queued_calls();
103-
104-
now = emscripten_get_now();
105-
double ms_to_sleep = target - now;
106-
if (ms_to_sleep < min_ms_slice_to_sleep)
107-
continue;
108-
if (ms_to_sleep > max_ms_slice_to_sleep)
109-
ms_to_sleep = max_ms_slice_to_sleep;
110-
emscripten_futex_wait(&dummyZeroAddress, 0, ms_to_sleep);
111-
now = emscripten_get_now();
112-
} while (now < target);
113-
114-
emscripten_conditional_set_current_thread_status(
115-
EM_THREAD_STATUS_SLEEPING, EM_THREAD_STATUS_RUNNING);
80+
// We include emscripten_current_thread_process_queued_calls before and
81+
// after sleeping since that is how we recieve "async" signals.
82+
// We include __pthread_testcancel here becuase clock_nanosleep is
83+
// a pthread cancelation point.
84+
emscripten_current_thread_process_queued_calls();
85+
__pthread_testcancel();
86+
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING,
87+
EM_THREAD_STATUS_SLEEPING);
88+
uint32_t dummyZeroAddress = 0;
89+
emscripten_futex_wait(&dummyZeroAddress, 0, msecs);
90+
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_SLEEPING,
91+
EM_THREAD_STATUS_RUNNING);
92+
emscripten_current_thread_process_queued_calls();
93+
__pthread_testcancel();
11694
}
11795

11896
static struct pthread __main_pthread;

system/lib/pthread/library_pthread_stub.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ int pthread_cancel(pthread_t thread) {
231231
return 0;
232232
}
233233

234-
void pthread_testcancel() {}
234+
void __pthread_testcancel() {}
235+
236+
weak_alias(__pthread_testcancel, pthread_testcancel);
235237

236238
_Noreturn void __pthread_exit(void* status) {
237239
exit(0);

test/codesize/test_codesize_minimal_pthreads.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.out.js": 7363,
33
"a.out.js.gz": 3604,
4-
"a.out.nodebug.wasm": 19046,
5-
"a.out.nodebug.wasm.gz": 8821,
6-
"total": 26409,
7-
"total_gz": 12425,
4+
"a.out.nodebug.wasm": 19003,
5+
"a.out.nodebug.wasm.gz": 8786,
6+
"total": 26366,
7+
"total_gz": 12390,
88
"sent": [
99
"a (memory)",
1010
"b (exit)",

test/codesize/test_codesize_minimal_pthreads_memgrowth.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.out.js": 7765,
33
"a.out.js.gz": 3810,
4-
"a.out.nodebug.wasm": 19047,
5-
"a.out.nodebug.wasm.gz": 8822,
6-
"total": 26812,
7-
"total_gz": 12632,
4+
"a.out.nodebug.wasm": 19004,
5+
"a.out.nodebug.wasm.gz": 8787,
6+
"total": 26769,
7+
"total_gz": 12597,
88
"sent": [
99
"a (memory)",
1010
"b (exit)",

test/other/test_itimer.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ void prof_handler(int dummy) {
3333
}
3434

3535
void test_oneoff(int which) {
36+
printf("test_oneoff\n");
3637
memset(got_alarm, 0, sizeof(got_alarm));
3738

3839
int rtn;
3940
struct itimerval val;
4041
memset(&val, 0, sizeof(val));
4142

42-
// Set a timer for 1 second
43-
val.it_value.tv_sec = 1;
43+
// Set a non-repeating timer for 500 milliseconds
44+
val.it_value.tv_usec = 500 * 1000;
4445
rtn = setitimer(which, &val, NULL);
4546
assert(rtn == 0);
4647

@@ -59,9 +60,9 @@ void test_oneoff(int which) {
5960
assert(val.it_value.tv_sec == 0);
6061
assert(val.it_value.tv_usec > 0);
6162

62-
// Wait 1.5s
63+
// Wait 800ms
6364
assert(!got_alarm[which]);
64-
usleep(1500 * 1000);
65+
usleep(700 * 1000);
6566

6667
// Verify that the time fired and is no longer active
6768
assert(got_alarm[which]);
@@ -74,6 +75,8 @@ void test_oneoff(int which) {
7475
#define ERROR_MARGIN 3
7576

7677
void test_sequence(int which) {
78+
int64_t ms_to_sleep = NUM_TIMERS * 100 + 50;
79+
printf("test_sequence (sleeping for %lldms)\n", ms_to_sleep);
7780
memset(got_alarm, 0, sizeof(got_alarm));
7881
// Set a timer to fire every 100ms
7982
struct itimerval val;
@@ -83,7 +86,7 @@ void test_sequence(int which) {
8386
val.it_interval.tv_usec = 100 * 1000;
8487
int rtn = setitimer(which, &val, NULL);
8588
// Sleep for a little over NUM_TIMERS * 100ms
86-
usleep((NUM_TIMERS * 100 + 50) * 1000);
89+
usleep(ms_to_sleep * 1000);
8790
printf("got %d alarms\n", got_alarm[which]);
8891
// Normally we would expect NUM_TIMERS to fire in this time
8992
// but leave some wiggle room for scheduling anomalies.

test/pthread/test_pthread_kill.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,26 @@
55

66
#include <pthread.h>
77
#include <sys/types.h>
8+
#include <stdbool.h>
89
#include <stdio.h>
910
#include <stdlib.h>
1011
#include <assert.h>
1112
#include <unistd.h>
1213
#include <errno.h>
1314
#include <signal.h>
1415

16+
#include <emscripten/console.h>
17+
1518
pthread_cond_t started_cond = PTHREAD_COND_INITIALIZER;
1619
pthread_mutex_t started_lock = PTHREAD_MUTEX_INITIALIZER;
17-
_Atomic int got_term_signal = 0;
20+
_Atomic bool got_term_signal = false;
1821

1922
pthread_t thr;
2023

2124
void signal_handler(int sig, siginfo_t * info, void * arg) {
2225
printf("signal: %d onthread=%d\n", sig, pthread_self() == thr);
2326
if (sig == SIGTERM) {
24-
got_term_signal = 1;
27+
got_term_signal = true;
2528
}
2629
}
2730

@@ -63,9 +66,9 @@ int main() {
6366
printf("thread has started, sending SIGTERM\n");
6467

6568
s = pthread_kill(thr, SIGTERM);
69+
assert(s == 0);
6670
printf("SIGTERM sent\n");
6771

68-
assert(s == 0);
6972

7073
pthread_join(thr, NULL);
7174
return 0;

0 commit comments

Comments
 (0)