From f19221b936ceb4f1bb76672a4b927a896cf8e3b6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2019 13:03:56 -0800 Subject: [PATCH 1/2] wip --- src/library.js | 2 +- src/preamble.js | 1 - src/shell_pthreads.js | 8 ++++---- src/worker.js | 12 +++++------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/library.js b/src/library.js index 236b604b4e8df..825aa95972c76 100644 --- a/src/library.js +++ b/src/library.js @@ -4003,7 +4003,7 @@ LibraryManager.library = { #if USE_PTHREADS // Pthreads need their clocks synchronized to the execution of the main thread, so give them a special form of the function. "if (ENVIRONMENT_IS_PTHREAD) {\n" + - " _emscripten_get_now = function() { return performance['now']() - __performance_now_clock_drift; };\n" + + " _emscripten_get_now = function() { return performance['now']() - Module['__performance_now_clock_drift']; };\n" + "} else " + #endif "if (typeof dateNow !== 'undefined') {\n" + diff --git a/src/preamble.js b/src/preamble.js index aac253b849153..c16b1ca500925 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -67,7 +67,6 @@ var wasmModule; // to avoid accessing the global scope. var threadInfoStruct = 0; var selfThreadId = 0; -var __performance_now_clock_drift = 0; #if WASM_BACKEND var tempDoublePtr = 0; #endif diff --git a/src/shell_pthreads.js b/src/shell_pthreads.js index e57562e844451..b6c05ba93db41 100644 --- a/src/shell_pthreads.js +++ b/src/shell_pthreads.js @@ -7,10 +7,10 @@ var ENVIRONMENT_IS_PTHREAD = Module['ENVIRONMENT_IS_PTHREAD'] || false; if (ENVIRONMENT_IS_PTHREAD) { // Grab imports from the pthread to local scope. - buffer = {{{EXPORT_NAME}}}['buffer']; - tempDoublePtr = {{{EXPORT_NAME}}}['tempDoublePtr']; - DYNAMIC_BASE = {{{EXPORT_NAME}}}['DYNAMIC_BASE']; - DYNAMICTOP_PTR = {{{EXPORT_NAME}}}['DYNAMICTOP_PTR']; + buffer = Module['buffer']; + tempDoublePtr = Module['tempDoublePtr']; + DYNAMIC_BASE = Module['DYNAMIC_BASE']; + DYNAMICTOP_PTR = Module['DYNAMICTOP_PTR']; // Note that not all runtime fields are imported above. Values for STACK_BASE, STACKTOP and STACK_MAX are not yet known at worker.js load time. // These will be filled in at pthread startup time (the 'run' message for a pthread - pthread start establishes the stack frame) } diff --git a/src/worker.js b/src/worker.js index 13aba844d5a3d..9c304f7f72a44 100644 --- a/src/worker.js +++ b/src/worker.js @@ -17,12 +17,6 @@ var tempDoublePtr = 0; // A temporary memory area for global float and double ma var noExitRuntime; -// performance.now() is specced to return a wallclock time in msecs since that Web Worker/main thread launched. However for pthreads this can cause -// subtle problems in emscripten_get_now() as this essentially would measure time from pthread_create(), meaning that the clocks between each threads -// would be wildly out of sync. Therefore sync all pthreads to the clock on the main browser thread, so that different threads see a somewhat -// coherent clock across each of them (+/- 0.1msecs in testing) -var __performance_now_clock_drift = 0; - // Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091 // Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print. var Module = {}; @@ -163,7 +157,11 @@ this.onmessage = function(e) { } else if (e.data.cmd === 'objectTransfer') { PThread.receiveObjectTransfer(e.data); } else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point. - __performance_now_clock_drift = performance.now() - e.data.time; // Sync up to the clock of the main thread. + // performance.now() is specced to return a wallclock time in msecs since that Web Worker/main thread launched. However for pthreads this can cause + // subtle problems in emscripten_get_now() as this essentially would measure time from pthread_create(), meaning that the clocks between each threads + // would be wildly out of sync. Therefore sync all pthreads to the clock on the main browser thread, so that different threads see a somewhat + // coherent clock across each of them (+/- 0.1msecs in testing) + Module['__performance_now_clock_drift'] = performance.now() - e.data.time; threadInfoStruct = e.data.threadInfoStruct; Module['__register_pthread_ptr'](threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out. selfThreadId = e.data.selfThreadId; From f6dc61a7a07ccbaeab9b343a5bd898fc401caf82 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2019 13:17:46 -0800 Subject: [PATCH 2/2] test --- tests/pthread/test_pthread_reltime.cpp | 87 ++++++++++++++++++++++++++ tests/test_browser.py | 7 +++ 2 files changed, 94 insertions(+) create mode 100644 tests/pthread/test_pthread_reltime.cpp diff --git a/tests/pthread/test_pthread_reltime.cpp b/tests/pthread/test_pthread_reltime.cpp new file mode 100644 index 0000000000000..23a9ac50a2e9e --- /dev/null +++ b/tests/pthread/test_pthread_reltime.cpp @@ -0,0 +1,87 @@ +// Copyright 2019 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. + +#include +#include +#include + +typedef std::chrono::milliseconds::rep TimePoint; + +TimePoint now() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +static TimePoint ping, pong; + +static std::mutex mutex; +static std::condition_variable cond_var; + +void *thread_main(void *arg) { + std::cout << "running thread ..." << std::endl; + + std::unique_lock lock(mutex); + + cond_var.wait(lock); + + // Measure time in the pthread. + pong = now(); + + std::cout << "pong - ping: " << (now() - ping) << std::endl; + + cond_var.notify_one(); + + return NULL; +} + +EMSCRIPTEN_KEEPALIVE +extern "C" int notify() { + { + std::unique_lock lock(mutex); + + std::cout << "notifying ..." << std::endl; + + ping = now(); + + cond_var.notify_one(); + } + { + std::unique_lock lock(mutex); + + cond_var.wait(lock); + + TimePoint last = now(); + + std::cout << "last - pong: " << (last - ping) << std::endl; + + // Time measured on a worker should be relative to the main thread, + // so that things are basically monotonic. + + REPORT_RESULT(int(pong >= ping) + 2 * int(last >= pong)); + } + + return 0; +} + +int main() { + std::cout << "running main ..." << std::endl; + + EM_ASM({ + setTimeout(function() { + Module._notify(); + }); + }); + + pthread_attr_t attr; + pthread_attr_init(&attr); + + pthread_t thread; + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int error = pthread_create(&thread, &attr, thread_main, NULL); + if (error) + abort(); + + return 0; +} diff --git a/tests/test_browser.py b/tests/test_browser.py index 87b8ecc209768..a9f570294722d 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -4567,6 +4567,13 @@ def run(emcc_args=[]): run(['-s', 'ASSERTIONS=1']) run(['-s', 'PROXY_TO_PTHREAD=1']) + # Tests that time in a pthread is relative to the main thread, so measurements + # on different threads are still monotonic, as if checking a single central + # clock. + @requires_threads + def test_pthread_reltime(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_reltime.cpp'), expected='3', args=['-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=1']) + # Tests that it is possible to load the main .js file of the application manually via a Blob URL, and still use pthreads. @requires_threads def test_load_js_from_blob_with_pthreads(self):