From d79256df649d42c19348d2f1faa4bb6e6c6cd172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 5 Feb 2018 17:56:39 +0200 Subject: [PATCH] Expose internal do_sleep() as a public emscripten_thread_sleep() function --- system/include/emscripten/threading.h | 12 +++++++ system/lib/pthread/library_pthread.c | 42 +++++++++++++++---------- tests/pthread/emscripten_thread_sleep.c | 41 ++++++++++++++++++++++++ tests/test_browser.py | 5 +++ 4 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 tests/pthread/emscripten_thread_sleep.c diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h index 9235fc4ecc401..17b96b40a519b 100644 --- a/system/include/emscripten/threading.h +++ b/system/include/emscripten/threading.h @@ -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 diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c index 8fe9888ee4fa4..d59936724fb28 100644 --- a/system/lib/pthread/library_pthread.c +++ b/system/lib/pthread/library_pthread.c @@ -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(); + + // 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; } diff --git a/tests/pthread/emscripten_thread_sleep.c b/tests/pthread/emscripten_thread_sleep.c new file mode 100644 index 0000000000000..38a27868835a9 --- /dev/null +++ b/tests/pthread/emscripten_thread_sleep.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +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); +} diff --git a/tests/test_browser.py b/tests/test_browser.py index 44ceab85d45ca..beb2b1c2f17b6 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -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()