You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a new ALLOW_BLOCKING_ON_MAIN_THREAD option, set to
1 by default. If unset, it will throw when blocking on the main thread.
If 1 as in the default case, we warn in the console about possible
issues and link to the docs about that.
This ignores things like mutexes which are usually brief, and checks
for pthread_join or pthread_cond_wait operations, which tend to be
longer and run the risk of Web-specific proxying-related deadlocks.
Add pthread_tryjoin_np which does a non-blocking join, which always
works on the main thread.
Improve the docs in this area.
Copy file name to clipboardExpand all lines: site/source/docs/porting/pthreads.rst
+70-4Lines changed: 70 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -43,17 +43,83 @@ but is unrelated. That flag does not use pthreads or SharedArrayBuffer, and
43
43
instead uses a plain Web Worker to run your main program (and postMessage to
44
44
proxy messages back and forth).
45
45
46
+
Proxying
47
+
========
48
+
49
+
The Web allows certain operations to only happen from the main browser thread,
50
+
like interacting with the DOM. As a result, various operations are proxied to
51
+
the main browser thread if they are called on a background thread. See
52
+
`bug 3495 <https://github.com/emscripten-core/emscripten/issues/3495>`_ for
53
+
more information and how to try to work around this until then. To check which
54
+
operations are proxied, you can look for the function's implementation in
55
+
the JS library (``src/library_*``) and see if it is annotated with
56
+
``__proxy: 'sync'`` or ``__proxy: 'async'``; however, note that the browser
57
+
itself proxies certain things (like some GL operations), so there is no
58
+
general way to be safe here (aside from not blocking on the main browser
59
+
thread).
60
+
61
+
In addition, Emscripten currently has a simple model of file I/O only happening
62
+
on the main application thread (as we support JS plugin filesystems, which
63
+
cannot share memory); this is another set of operations that are proxied.
64
+
65
+
Proxying can cause problems in certain cases, see the section on blocking below.
66
+
67
+
Blocking on the main browser thread
68
+
===================================
69
+
70
+
Note that in most cases the "main browser thread" is the same as the "main
71
+
application thread". The main browser thread is where web pages start to run
72
+
JavaScript, and where JavaScript can access the DOM (a page can also create a Web
73
+
Worker, which would no longer be on the main thread). The main application
74
+
thread is the one on which you started up the application (by loading the main
75
+
JS file emitted by Emscripten). If you started it on the main browser thread -
76
+
by it being a normal HTML page - then the two are identical. However, you can
77
+
also start a multithreaded application in a worker; in that case the main
78
+
application thread is that worker, and there is no access to the main browser
79
+
thread.
80
+
81
+
The Web API for atomics does not allow blocking on the main thread
82
+
(specifically, ``Atomics.wait`` doesn't work there). Such blocking is
83
+
necessary in APIs like ``pthread_join`` and anything that uses a futex wait
84
+
under the hood, like ``usleep()``, ``emscripten_futex_wait()``, or
85
+
``pthread_mutex_lock()``. To make them work, we use a busy-wait on the main
86
+
browser thread, which can make the browser tab unresponsive, and also wastes
87
+
power. (On a pthread, this isn't a problem as it runs in a Web Worker, where
88
+
we don't need to busy-wait.)
89
+
90
+
Busy-waiting on the main browser thread in general will work despite the
91
+
downsides just mentioned, for things like waiting on a lightly-contended mutex.
92
+
However, things like ``pthread_join`` and ``pthread_cond_wait``
93
+
are often intended to block for long periods of time, and if that
94
+
happens on the main browser thread, and while other threads expect it to
95
+
respond, it can cause a surprising deadlock. That can happen because of
96
+
proxying, see the previous section. If the main thread blocks while a worker
97
+
attempts to proxy to it, a deadlock can occur.
98
+
99
+
The bottom line is that on the Web it is bad for the main browser thread to
100
+
wait on anything else. Therefore by default Emscripten warns if
101
+
``pthread_join`` and ``pthread_cond_wait`` happen on the main browser thread,
102
+
and will throw an error if ``ALLOW_BLOCKING_ON_MAIN_THREAD`` is zero
103
+
(whose message will point to here).
104
+
105
+
To avoid these problems, you can use ``PROXY_TO_PTHREAD``, which as
106
+
mentioned earlier moves your ``main()`` function to a pthread, which leaves
107
+
the main browser thread to focus only on receiving proxied events. This is
108
+
recommended in general, but may take some porting work, if the application
109
+
assumed ``main()`` was on the main browser thread.
110
+
111
+
Another option is to replace blocking calls with nonblocking ones. For example
112
+
you can replace ``pthread_join`` with ``pthread_tryjoin_np``. This may require
113
+
your application to be refactored to use asynchronous events, perhaps through
114
+
:c:func:`emscripten_set_main_loop` or :ref:`Asyncify`.
115
+
46
116
Special considerations
47
117
======================
48
118
49
119
The Emscripten implementation for the pthreads API should follow the POSIX standard closely, but some behavioral differences do exist:
50
120
51
121
- When the linker flag ``-s PTHREAD_POOL_SIZE=<integer>`` is not specified and ``pthread_create()`` is called, the new thread will not start until control is yielded back to the browser's main event loop, because the web worker cannot be created while JS or wasm code is running. This is a violation of POSIX behavior and will break common code which creates a thread and immediately joins it or otherwise synchronously waits to observe an effect such as a memory write. Using a pool creates the web workers before main is called, allowing thread creation to be synchronous.
52
122
53
-
- Browser DOM access is only possible on the main browser thread, and therefore things that may access the DOM, like filesystem operations (``fopen()``, etc.) or changing the HTML page's title, etc., are proxied over to the main browser thread. This proxying can generate a deadlock in a special situation that native code running pthreads does not have. See `bug 3495 <https://github.com/emscripten-core/emscripten/issues/3495>`_ for more information and how to work around this until this proxying is no longer needed in Emscripten. (To check which operations are proxied, you can look for the function's implementation in the JS library (``src/library_*``) and see if it is annotated with ``__proxy: 'sync'`` or ``__proxy: 'async'``.)
54
-
55
-
- When doing a futex wait, e.g. ``usleep()``, ``emscripten_futex_wait()``, or ``pthread_mutex_lock()``, we use ``Atomics.wait`` on workers, which the browser should do pretty efficiently. But that is not available on the main thread, and so we busy-wait there. Busy-waiting is not recommended because it freezes the tab, and also wastes power.
56
-
57
123
- The Emscripten implementation does not support `POSIX signals <http://man7.org/linux/man-pages/man7/signal.7.html>`_, which are sometimes used in conjunction with pthreads. This is because it is not possible to send signals to web workers and pre-empt their execution. The only exception to this is pthread_kill() which can be used as normal to forcibly terminate a running thread.
58
124
59
125
- The Emscripten implementation does also not support multiprocessing via ``fork()`` and ``join()``.
warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
809
+
#endif
810
+
#if !ALLOW_BLOCKING_ON_MAIN_THREAD
811
+
abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
// Copyright 2019 The Emscripten Authors. All rights reserved.
2
+
// Emscripten is available under two separate licenses, the MIT license and the
3
+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4
+
// found in the LICENSE file.
5
+
6
+
#include<assert.h>
7
+
#include<emscripten.h>
8
+
#include<pthread.h>
9
+
#include<stdio.h>
10
+
11
+
#include<atomic>
12
+
13
+
pthread_t thread;
14
+
15
+
std::atomic<int> tries;
16
+
17
+
staticconstint EXPECTED_TRIES = 7;
18
+
19
+
voidloop() {
20
+
void* retval;
21
+
printf("try...\n");
22
+
if (pthread_tryjoin_np(thread, &retval) == 0) {
23
+
emscripten_cancel_main_loop();
24
+
assert(tries.load() == EXPECTED_TRIES);
25
+
#ifdef REPORT_RESULT
26
+
REPORT_RESULT(2);
27
+
#endif
28
+
}
29
+
tries++;
30
+
}
31
+
32
+
void *ThreadMain(void *arg) {
33
+
#ifdef TRY_JOIN
34
+
// Delay to force the main thread to try and fail a few times before
35
+
// succeeding.
36
+
while (tries.load() < EXPECTED_TRIES) {}
37
+
#endif
38
+
pthread_exit((void*)0);
39
+
}
40
+
41
+
pthread_tCreateThread() {
42
+
pthread_t ret;
43
+
int rc = pthread_create(&ret, NULL, ThreadMain, (void*)0);
44
+
assert(rc == 0);
45
+
return ret;
46
+
}
47
+
48
+
intmain() {
49
+
if (!emscripten_has_threading_support()) {
50
+
#ifdef REPORT_RESULT
51
+
REPORT_RESULT(0);
52
+
#endif
53
+
printf("Skipped: Threading is not supported.\n");
54
+
return0;
55
+
}
56
+
57
+
int x = EM_ASM_INT({
58
+
onerror = function(e) {
59
+
var message = e.toString();
60
+
var success = message.indexOf("Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread") >= 0;
61
+
if (success && !Module.reported) {
62
+
Module.reported = true;
63
+
console.log("reporting success");
64
+
// manually REPORT_RESULT; we shouldn't call back into native code at this point
// Copyright 2019 The Emscripten Authors. All rights reserved.
2
+
// Emscripten is available under two separate licenses, the MIT license and the
3
+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4
+
// found in the LICENSE file.
5
+
6
+
#include<assert.h>
7
+
#include<emscripten.h>
8
+
#include<pthread.h>
9
+
#include<stdio.h>
10
+
11
+
intmain() {
12
+
if (!emscripten_has_threading_support())
13
+
{
14
+
#ifdef REPORT_RESULT
15
+
REPORT_RESULT(0);
16
+
#endif
17
+
printf("Skipped: Threading is not supported.\n");
18
+
return0;
19
+
}
20
+
21
+
int x = EM_ASM_INT({
22
+
onerror = function(e) {
23
+
var message = e.toString();
24
+
var success = message.indexOf("Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread") >= 0;
25
+
if (success && !Module.reported) {
26
+
Module.reported = true;
27
+
console.log("reporting success");
28
+
// manually REPORT_RESULT; we shouldn't call back into native code at this point
0 commit comments