Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions system/include/emscripten/threading.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ pthread_t emscripten_main_browser_thread_id(void);
// Direct syscall access, second argument is a varargs pointer. used in proxying
int emscripten_syscall(int, void*);

// Synchronously sleeps the calling thread for the given number of milliseconds.
// Note: Calling this on the main browser thread is _very_ _very_ bad for application logic throttling,
// because it does not save any battery, it will spin up the CPU at 100%, lock up the UI, printfs will not come
// through on web page or the console, and eventually it will show up the slow script dialog.
// Calling this function in a pthread (Web Worker) is fine, and a good way to go if you need to synchronously
// sleep for a specific amount of time while saving power.
// Note 2: This function will process the pthread-specific event queue for the calling thread while sleeping,
// and this function also acts as a cancellation point.
// Note 3: This function is enabled when targeting pthreads (SharedArrayBuffer), not to be confused with
// similarly named function emscripten_sleep(), which is intended for Asyncify and Emterpreter builds.
void emscripten_thread_sleep(double msecs);

#define EM_THREAD_STATUS int
#define EM_THREAD_STATUS_NOTSTARTED 0
#define EM_THREAD_STATUS_RUNNING 1
Expand Down
42 changes: 25 additions & 17 deletions system/lib/pthread/library_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,40 +123,48 @@ int pthread_getattr_np(pthread_t t, pthread_attr_t *a)

static uint32_t dummyZeroAddress = 0;

static void do_sleep(double msecs)
void emscripten_thread_sleep(double msecs)
{
int is_main_thread = emscripten_is_main_runtime_thread();
double now = emscripten_get_now();
double target = now + msecs;
#ifdef __EMSCRIPTEN__

__pthread_testcancel(); // pthreads spec: sleep is a cancellation point, so must test if this thread is cancelled during the sleep.
emscripten_current_thread_process_queued_calls();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: this is defined in #6201 (Multithreading 29/N)

// If we have less than this many msecs left to wait, busy spin that instead.
const double minimumTimeSliceToSleep = 0.1;

// main thread may need to run proxied calls, so sleep in very small slices to be responsive.
const double maxMsecsSliceToSleep = emscripten_is_main_browser_thread() ? 1 : 100;

emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_SLEEPING);
#endif
while(now < target) {
if (is_main_thread) emscripten_main_thread_process_queued_calls(); // Assist other threads by executing proxied operations that are effectively singlethreaded.
__pthread_testcancel(); // pthreads spec: usleep is a cancellation point, so it must test if this thread is cancelled during the sleep.
now = emscripten_get_now();
while(now < target)
{
// Keep processing the main loop of the calling thread.
__pthread_testcancel(); // pthreads spec: sleep is a cancellation point, so must test if this thread is cancelled during the sleep.
emscripten_current_thread_process_queued_calls();

now = emscripten_get_now();
double msecsToSleep = target - now;
if (msecsToSleep > 1.0) {
if (msecsToSleep > 100.0) msecsToSleep = 100.0;
if (is_main_thread && msecsToSleep > 1) msecsToSleep = 1; // main thread may need to run proxied calls, so sleep in very small slices to be responsive.
emscripten_futex_wait(&dummyZeroAddress, 0, msecsToSleep);
}
}
#ifdef __EMSCRIPTEN__
if (msecsToSleep > maxMsecsSliceToSleep) msecsToSleep = maxMsecsSliceToSleep;
if (msecsToSleep >= minimumTimeSliceToSleep) emscripten_futex_wait(&dummyZeroAddress, 0, msecsToSleep);
now = emscripten_get_now();
};

emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_SLEEPING, EM_THREAD_STATUS_RUNNING);
#endif
}

int nanosleep(const struct timespec *req, struct timespec *rem)
{
if (!req || req->tv_nsec < 0 || req->tv_nsec > 999999999L || req->tv_sec < 0) return EINVAL;
do_sleep(req->tv_sec * 1000.0 + req->tv_nsec / 1e6);
emscripten_thread_sleep(req->tv_sec * 1000.0 + req->tv_nsec / 1e6);
return 0;
}

int usleep(unsigned usec)
{
do_sleep(usec / 1e3);
emscripten_thread_sleep(usec / 1e3);
return 0;
}

Expand Down
41 changes: 41 additions & 0 deletions tests/pthread/emscripten_thread_sleep.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <pthread.h>
#include <emscripten.h>
#include <emscripten/threading.h>
#include <assert.h>

void Sleep(double msecs)
{
double t1 = emscripten_get_now();
emscripten_thread_sleep(msecs);
double t2 = emscripten_get_now();
printf("emscripten_thread_sleep() slept for %f msecs.\n", t2 - t1);

assert(t2 - t1 >= 0.9 * msecs); // Should have slept ~ the requested time.
}

void *thread_main(void *arg)
{
Sleep(1);
Sleep(10);
Sleep(100);
Sleep(1000);
Sleep(5000);

#ifdef REPORT_RESULT
REPORT_RESULT(1);
#endif
return 0;
}

int main()
{
// Bad bad bad to sleep on the main thread, but test that it works.
Sleep(1);
Sleep(10);
Sleep(100);
Sleep(1000);
Sleep(5000);
pthread_t thread;
pthread_create(&thread, NULL, thread_main, NULL);
EM_ASM(Module['noExitRuntime']=true);
}
5 changes: 5 additions & 0 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4126,6 +4126,11 @@ def test_unicode_html_shell(self):
subprocess.check_output([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp'), '--shell-file', 'shell.html', '-o', 'test.html'])
self.run_browser('test.html', None, '/report_result?0')

# Tests the functionality of the emscripten_thread_sleep() function.
@requires_threads
def test_emscripten_thread_sleep(self):
self.btest(path_from_root('tests', 'pthread', 'emscripten_thread_sleep.c'), expected='1', args=['-s', 'USE_PTHREADS=1'])

# Tests that Emscripten-compiled applications can be run from a relative path in browser that is different than the address of the current page
def test_browser_run_from_different_directory(self):
src = open(path_from_root('tests', 'browser_test_hello_world.c')).read()
Expand Down