Skip to content

Commit 30db8fc

Browse files
committed
Fixing POSIX testsuite pthread test fails
This patch aims for fixing failures in pthread related POSIX test from clone of http://posixtest.sourceforge.net/ Following problems are addressed: 1. pthread_cond_wait/2-3 test hang - Disabling recursive thread cancellation. - Allowing __timedwait_cp to be true cancellation point as _cp suffix suggests 2. pthread_getschedparam/1-3 test hangs sometimes: In pthread_barrier_wait adding check if lock is held by main thread and waiting on futex in small slices of time there, to check if there is some work to do on behalf of Worker Threads. Signed-off-by: Adam Bujalski <[email protected]>
1 parent 1050ed3 commit 30db8fc

File tree

7 files changed

+147
-25
lines changed

7 files changed

+147
-25
lines changed

src/library_pthread.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,24 @@ var LibraryPThread = {
178178
if (ENVIRONMENT_IS_PTHREAD && _pthread_self()) ___pthread_tsd_run_dtors();
179179
},
180180

181+
onThreadExit: function(tb, exitCode) {
182+
// Disable all cancellation so that executing the cleanup handlers won't trigger another JS
183+
// canceled exception to be thrown.
184+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/);
185+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/);
186+
PThread.runExitHandlers();
187+
188+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode);
189+
// When we publish this, the main thread is free to deallocate the thread object and we are done.
190+
// Therefore set _pthread_self = 0; above to 'release' the object in this worker thread.
191+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running.
192+
193+
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads
194+
195+
// Not hosting a pthread anymore in this worker, reset the info structures to null.
196+
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope.
197+
},
198+
181199
// Called when we are performing a pthread_exit(), either explicitly called
182200
// by programmer, or implicitly when leaving the thread main function.
183201
threadExit: function(exitCode) {
@@ -191,19 +209,8 @@ var LibraryPThread = {
191209
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0);
192210
_free(profilerBlock);
193211
#endif
194-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode);
195-
// When we publish this, the main thread is free to deallocate the thread object and we are done.
196-
// Therefore set _pthread_self = 0; above to 'release' the object in this worker thread.
197-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1);
212+
PThread.onThreadExit(tb, exitCode);
198213

199-
// Disable all cancellation so that executing the cleanup handlers won't trigger another JS
200-
// canceled exception to be thrown.
201-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/);
202-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/);
203-
PThread.runExitHandlers();
204-
205-
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
206-
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope.
207214
if (ENVIRONMENT_IS_PTHREAD) {
208215
// Note: in theory we would like to return any offscreen canvases back to the main thread,
209216
// but if we ever fetched a rendering context for them that would not be valid, so we don't try.
@@ -213,13 +220,8 @@ var LibraryPThread = {
213220
},
214221

215222
threadCancel: function() {
216-
PThread.runExitHandlers();
217223
var tb = _pthread_self();
218-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, -1/*PTHREAD_CANCELED*/);
219-
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running.
220-
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads
221-
// Not hosting a pthread anymore in this worker, reset the info structures to null.
222-
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope.
224+
PThread.onThreadExit(tb, -1/*PTHREAD_CANCELED*/);
223225
postMessage({ 'cmd': 'cancelDone' });
224226
},
225227

system/lib/libc/musl/src/thread/__timedwait.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ int __timedwait_cp(volatile int *addr, int val,
4242
#ifdef __EMSCRIPTEN__
4343
double msecsToSleep = top ? (top->tv_sec * 1000 + top->tv_nsec / 1000000.0) : INFINITY;
4444
int is_main_thread = emscripten_is_main_browser_thread();
45-
if (is_main_thread || pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
45+
// cp suffix in the function name means "cancellation point", so this wait can be cancelled
46+
// by the users unless current threads cancelability is set to PTHREAD_CANCEL_DISABLE
47+
// which may be either done by the user of __timedwait() function.
48+
if (is_main_thread ||
49+
pthread_self()->canceldisable != PTHREAD_CANCEL_DISABLE ||
50+
pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
4651
double sleepUntilTime = emscripten_get_now() + msecsToSleep;
4752
do {
4853
if (_pthread_isduecanceled(pthread_self())) {

system/lib/libc/musl/src/thread/pthread_barrier_wait.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,29 @@ int pthread_barrier_wait(pthread_barrier_t *b)
8787
while (spins-- && !inst->finished)
8888
a_spin();
8989
a_inc(&inst->finished);
90-
while (inst->finished == 1) {
9190
#ifdef __EMSCRIPTEN__
92-
emscripten_futex_wait(&inst->finished, 1, INFINITY);
91+
int is_main_thread = emscripten_is_main_runtime_thread();
92+
while (inst->finished == 1) {
93+
if (is_main_thread) {
94+
int e;
95+
do {
96+
// Main thread waits in _very_ small slices so that it stays responsive to assist proxied
97+
// pthread calls.
98+
e = emscripten_futex_wait(&inst->finished, 1, 1);
99+
// Assist other threads by executing proxied operations that are effectively singlethreaded.
100+
emscripten_main_thread_process_queued_calls();
101+
} while(e == -ETIMEDOUT);
102+
} else {
103+
// Can wait in one go.
104+
emscripten_futex_wait(&inst->finished, 1, INFINITY);
105+
}
106+
}
93107
#else
108+
while (inst->finished == 1) {
94109
__syscall(SYS_futex,&inst->finished,FUTEX_WAIT|128,1,0) != -ENOSYS
95110
|| __syscall(SYS_futex,&inst->finished,FUTEX_WAIT,1,0);
96-
#endif
97111
}
112+
#endif
98113
return PTHREAD_BARRIER_SERIAL_THREAD;
99114
}
100115

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2020 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 <pthread.h>
7+
#include <sys/types.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <assert.h>
11+
#include <unistd.h>
12+
#include <errno.h>
13+
#include <emscripten.h>
14+
#include <emscripten/threading.h>
15+
16+
pthread_barrier_t barrier;
17+
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
18+
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
19+
20+
int th_cancelled = 0;
21+
22+
volatile int res = 43;
23+
static void cleanup_handler(void *arg)
24+
{
25+
EM_ASM(out('Called clean-up handler with arg ' + $0), arg);
26+
int a = (int)arg;
27+
res -= a;
28+
29+
pthread_mutex_unlock(&mutex);
30+
pthread_barrier_wait(&barrier);
31+
}
32+
33+
static void *thread_start(void *arg)
34+
{
35+
pthread_cleanup_push(cleanup_handler, (void*)42);
36+
EM_ASM(out('Thread started!'));
37+
pthread_mutex_lock(&mutex);
38+
pthread_barrier_wait(&barrier);
39+
40+
int ret = 0;
41+
do {
42+
EM_ASM(out('Waiting on conditional variable'););
43+
ret = pthread_cond_wait(&condvar, &mutex);
44+
} while (ret == 0 && th_cancelled == 0);
45+
46+
if (ret != 0) {
47+
EM_ASM(out('Cond wait failed ret: ' + $0);, ret);
48+
}
49+
50+
res = 1000; // Shouldn't ever reach here.
51+
pthread_cleanup_pop(0);
52+
53+
pthread_mutex_unlock(&mutex);
54+
pthread_barrier_wait(&barrier);
55+
return NULL;
56+
}
57+
58+
pthread_t thr;
59+
60+
int main()
61+
{
62+
if (!emscripten_has_threading_support())
63+
{
64+
#ifdef REPORT_RESULT
65+
REPORT_RESULT(1);
66+
#endif
67+
printf("Skipped: Threading is not supported.\n");
68+
return 0;
69+
}
70+
71+
pthread_barrier_init(&barrier, nullptr, 2);
72+
73+
int s = pthread_create(&thr, NULL, thread_start, (void*)0);
74+
assert(s == 0);
75+
EM_ASM(out('Thread created'););
76+
77+
pthread_barrier_wait(&barrier);
78+
79+
// Lock mutex to ensure that thread is waiting
80+
pthread_mutex_lock(&mutex);
81+
82+
EM_ASM(out('Canceling thread..'););
83+
s = pthread_cancel(thr);
84+
assert(s == 0);
85+
th_cancelled = 1;
86+
pthread_mutex_unlock(&mutex);
87+
88+
EM_ASM(out('Main thread waitnig for side-thread'););
89+
pthread_barrier_wait(&barrier);
90+
pthread_barrier_destroy(&barrier);
91+
92+
EM_ASM(out('Test finished result: ' + $0);, res);
93+
#ifdef REPORT_RESULT
94+
REPORT_RESULT(res);
95+
#endif
96+
return res != 1;
97+
}

tests/runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,7 @@ def end_headers(self):
11481148
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
11491149
self.send_header('Cross-Origin-Resource-Policy', 'cross-origin')
11501150
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
1151+
11511152
return SimpleHTTPRequestHandler.end_headers(self)
11521153

11531154
def do_GET(self):

tests/test_browser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3756,6 +3756,11 @@ def test_std_thread_detach(self):
37563756
def test_pthread_cancel(self):
37573757
self.btest(path_from_root('tests', 'pthread', 'test_pthread_cancel.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS', '-s', 'PTHREAD_POOL_SIZE=8'])
37583758

3759+
# Test that pthread_cancel() cancels pthread_cond_wait() operation
3760+
@requires_threads
3761+
def test_pthread_cancel_cond_wait(self):
3762+
self.btest(path_from_root('tests', 'pthread', 'test_pthread_cancel_cond_wait.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8'])
3763+
37593764
# Test pthread_kill() operation
37603765
@no_chrome('pthread_kill hangs chrome renderer, and keep subsequent tests from passing')
37613766
@requires_threads

tests/test_posixtest.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,7 @@ def get_pthread_tests():
140140
**unsupported,
141141
'test_pthread_create_11_1': 'never returns',
142142
'test_pthread_barrier_wait_2_1': 'never returns',
143-
'test_pthread_cond_timedwait_2_6': 'never returns',
144-
'test_pthread_cond_timedwait_4_3': 'never returns',
145143
'test_pthread_attr_setscope_5_1': 'internally skipped (PTS_UNTESTED)',
146-
'test_pthread_cond_wait_2_3': 'never returns',
147144
'test_pthread_create_5_1': 'never returns',
148145
'test_pthread_exit_1_2': 'never returns',
149146
'test_pthread_exit_2_2': 'never returns',

0 commit comments

Comments
 (0)