Skip to content

Fix relative time in pthreads [v2] #10137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 6, 2020
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
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,5 @@ a license to everyone to use it as detailed in LICENSE.)
* John Granström <granstrom.john@gmail.com>
* Clemens Backes <clemensb@google.com> (copyright owned by Google, Inc.)
* Tibor Klajnscek <tiborkl@numfum.com>
* Benjamin Golinvaux <benjamin@golinvaux.com>
* Benjamin Golinvaux <benjamin@golinvaux.com>
* Niklas Fiekas <niklas.fiekas@backscattering.de>
2 changes: 1 addition & 1 deletion src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -4020,7 +4020,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 ENVIRONMENT_MAY_BE_SHELL
Expand Down
1 change: 0 additions & 1 deletion src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/shell_pthreads.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
20 changes: 12 additions & 8 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -155,8 +149,18 @@ this.onmessage = function(e) {
#endif
} 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.
} else if (e.data.cmd === 'run') {
// This worker was idle, and now should start executing its pthread entry
// point.
// 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;
Expand Down
82 changes: 82 additions & 0 deletions tests/pthread/test_pthread_reltime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 <iostream>
#include <ctime>
#include <condition_variable>
#include <pthread.h>
#include <emscripten.h>

static long now() {
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return time.tv_sec * 1000 + time.tv_nsec / 1000 / 1000;
}

static long ping, pong;

static std::mutex mutex;
static std::condition_variable cond_var;
static bool pong_requested = false;

void *thread_main(void *arg) {
std::cout << "running thread ..." << std::endl;

std::unique_lock<std::mutex> lock(mutex);
while (!pong_requested)
cond_var.wait(lock);
pong = now(); // Measure time in the pthread
std::cout << "pong - ping: " << (pong - ping) << std::endl;
pong_requested = false;
cond_var.notify_one();

return NULL;
}

EMSCRIPTEN_KEEPALIVE
extern "C" int notify() {
{
std::unique_lock<std::mutex> lock(mutex);
std::cout << "notifying ..." << std::endl;
ping = now();
pong_requested = true;
cond_var.notify_one();
}

{
std::unique_lock<std::mutex> lock(mutex);
while (pong_requested)
cond_var.wait(lock);
long 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;
}
7 changes: 7 additions & 0 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4584,6 +4584,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):
Expand Down