Skip to content

Commit e62a0df

Browse files
authored
[3.11] gh-95174: Add pthread stubs for WASI (GH-95234) (#95503)
Co-authored-by: Brett Cannon <[email protected]>. (cherry picked from commit 0fe645d) Co-authored-by: Christian Heimes <[email protected]>
1 parent 9af9ea2 commit e62a0df

17 files changed

+332
-41
lines changed

Doc/library/sys.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,8 @@ always available.
16581658
| | |
16591659
| | * ``'nt'``: Windows threads |
16601660
| | * ``'pthread'``: POSIX threads |
1661+
| | * ``'pthread-stubs'``: stub POSIX threads |
1662+
| | (on WebAssembly platforms without threading support) |
16611663
| | * ``'solaris'``: Solaris threads |
16621664
+------------------+---------------------------------------------------------+
16631665
| :const:`lock` | Name of the lock implementation: |

Include/cpython/pthread_stubs.h

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#ifndef Py_CPYTHON_PTRHEAD_STUBS_H
2+
#define Py_CPYTHON_PTRHEAD_STUBS_H
3+
4+
#if !defined(HAVE_PTHREAD_STUBS)
5+
# error "this header file requires stubbed pthreads."
6+
#endif
7+
8+
#ifndef _POSIX_THREADS
9+
# define _POSIX_THREADS 1
10+
#endif
11+
12+
/* Minimal pthread stubs for CPython.
13+
*
14+
* The stubs implement the minimum pthread API for CPython.
15+
* - pthread_create() fails.
16+
* - pthread_exit() calls exit(0).
17+
* - pthread_key_*() functions implement minimal TSS without destructor.
18+
* - all other functions do nothing and return 0.
19+
*/
20+
21+
#ifdef __wasi__
22+
// WASI's bits/alltypes.h provides type definitions when __NEED_ is set.
23+
// The header file can be included multiple times.
24+
# define __NEED_pthread_cond_t 1
25+
# define __NEED_pthread_condattr_t 1
26+
# define __NEED_pthread_mutex_t 1
27+
# define __NEED_pthread_mutexattr_t 1
28+
# define __NEED_pthread_key_t 1
29+
# define __NEED_pthread_t 1
30+
# define __NEED_pthread_attr_t 1
31+
# include <bits/alltypes.h>
32+
#else
33+
typedef struct { void *__x; } pthread_cond_t;
34+
typedef struct { unsigned __attr; } pthread_condattr_t;
35+
typedef struct { void *__x; } pthread_mutex_t;
36+
typedef struct { unsigned __attr; } pthread_mutexattr_t;
37+
typedef unsigned pthread_key_t;
38+
typedef unsigned pthread_t;
39+
typedef struct { unsigned __attr; } pthread_attr_t;
40+
#endif
41+
42+
// mutex
43+
PyAPI_FUNC(int) pthread_mutex_init(pthread_mutex_t *restrict mutex,
44+
const pthread_mutexattr_t *restrict attr);
45+
PyAPI_FUNC(int) pthread_mutex_destroy(pthread_mutex_t *mutex);
46+
PyAPI_FUNC(int) pthread_mutex_trylock(pthread_mutex_t *mutex);
47+
PyAPI_FUNC(int) pthread_mutex_lock(pthread_mutex_t *mutex);
48+
PyAPI_FUNC(int) pthread_mutex_unlock(pthread_mutex_t *mutex);
49+
50+
// condition
51+
PyAPI_FUNC(int) pthread_cond_init(pthread_cond_t *restrict cond,
52+
const pthread_condattr_t *restrict attr);
53+
PyAPI_FUNC(int) pthread_cond_destroy(pthread_cond_t *cond);
54+
PyAPI_FUNC(int) pthread_cond_wait(pthread_cond_t *restrict cond,
55+
pthread_mutex_t *restrict mutex);
56+
PyAPI_FUNC(int) pthread_cond_timedwait(pthread_cond_t *restrict cond,
57+
pthread_mutex_t *restrict mutex,
58+
const struct timespec *restrict abstime);
59+
PyAPI_FUNC(int) pthread_cond_signal(pthread_cond_t *cond);
60+
PyAPI_FUNC(int) pthread_condattr_init(pthread_condattr_t *attr);
61+
PyAPI_FUNC(int) pthread_condattr_setclock(
62+
pthread_condattr_t *attr, clockid_t clock_id);
63+
64+
// pthread
65+
PyAPI_FUNC(int) pthread_create(pthread_t *restrict thread,
66+
const pthread_attr_t *restrict attr,
67+
void *(*start_routine)(void *),
68+
void *restrict arg);
69+
PyAPI_FUNC(int) pthread_detach(pthread_t thread);
70+
PyAPI_FUNC(pthread_t) pthread_self(void);
71+
PyAPI_FUNC(int) pthread_exit(void *retval) __attribute__ ((__noreturn__));
72+
PyAPI_FUNC(int) pthread_attr_init(pthread_attr_t *attr);
73+
PyAPI_FUNC(int) pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
74+
PyAPI_FUNC(int) pthread_attr_destroy(pthread_attr_t *attr);
75+
76+
77+
// pthread_key
78+
#ifndef PTHREAD_KEYS_MAX
79+
# define PTHREAD_KEYS_MAX 128
80+
#endif
81+
82+
PyAPI_FUNC(int) pthread_key_create(pthread_key_t *key,
83+
void (*destr_function)(void *));
84+
PyAPI_FUNC(int) pthread_key_delete(pthread_key_t key);
85+
PyAPI_FUNC(void *) pthread_getspecific(pthread_key_t key);
86+
PyAPI_FUNC(int) pthread_setspecific(pthread_key_t key, const void *value);
87+
88+
#endif // Py_CPYTHON_PTRHEAD_STUBS_H

Include/cpython/pythread.h

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ PyAPI_FUNC(int) _PyThread_at_fork_reinit(PyThread_type_lock *lock);
2020
but hardcode the unsigned long to avoid errors for include directive.
2121
*/
2222
# define NATIVE_TSS_KEY_T unsigned long
23+
#elif defined(HAVE_PTHREAD_STUBS)
24+
# include "cpython/pthread_stubs.h"
25+
# define NATIVE_TSS_KEY_T pthread_key_t
2326
#else
2427
# error "Require native threads. See https://bugs.python.org/issue31370"
2528
#endif

Lib/test/pythoninfo.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,8 @@ def collect_socket(info_add):
588588

589589
try:
590590
hostname = socket.gethostname()
591-
except OSError:
592-
# WASI SDK 15.0 does not have gethostname(2).
591+
except (OSError, AttributeError):
592+
# WASI SDK 16.0 does not have gethostname(2).
593593
if sys.platform != "wasi":
594594
raise
595595
else:

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ def test_attributes(self):
626626
def test_thread_info(self):
627627
info = sys.thread_info
628628
self.assertEqual(len(info), 3)
629-
self.assertIn(info.name, ('nt', 'pthread', 'solaris', None))
629+
self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None))
630630
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
631631

632632
@unittest.skipUnless(support.is_emscripten, "only available on Emscripten")

Lib/test/test_threadsignals.py

+2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ def send_signals():
3636
os.kill(process_pid, signal.SIGUSR2)
3737
signalled_all.release()
3838

39+
3940
@threading_helper.requires_working_threading()
41+
@unittest.skipUnless(hasattr(signal, "alarm"), "test requires signal.alarm")
4042
class ThreadSignals(unittest.TestCase):
4143

4244
def test_signals(self):

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,7 @@ PYTHON_HEADERS= \
15731573
$(srcdir)/Include/cpython/objimpl.h \
15741574
$(srcdir)/Include/cpython/odictobject.h \
15751575
$(srcdir)/Include/cpython/picklebufobject.h \
1576+
$(srcdir)/Include/cpython/pthread_stubs.h \
15761577
$(srcdir)/Include/cpython/pyctype.h \
15771578
$(srcdir)/Include/cpython/pydebug.h \
15781579
$(srcdir)/Include/cpython/pyerrors.h \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
wasm32-wasi builds no longer depend on WASIX's pthread stubs. Python now has
2+
its own stubbed pthread API.

Python/thread.c

+13-5
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,15 @@ _PyThread_debug_deprecation(void)
9393
#endif
9494
}
9595

96-
#if defined(_POSIX_THREADS)
97-
# define PYTHREAD_NAME "pthread"
96+
#if defined(HAVE_PTHREAD_STUBS)
97+
# define PYTHREAD_NAME "pthread-stubs"
98+
# include "thread_pthread_stubs.h"
99+
#elif defined(_POSIX_THREADS)
100+
# if defined(__EMSCRIPTEN__) || !defined(__EMSCRIPTEN_PTHREADS__)
101+
# define PYTHREAD_NAME "pthread-stubs"
102+
# else
103+
# define PYTHREAD_NAME "pthread"
104+
# endif
98105
# include "thread_pthread.h"
99106
#elif defined(NT_THREADS)
100107
# define PYTHREAD_NAME "nt"
@@ -208,7 +215,9 @@ PyThread_GetInfo(void)
208215
}
209216
PyStructSequence_SET_ITEM(threadinfo, pos++, value);
210217

211-
#ifdef _POSIX_THREADS
218+
#ifdef HAVE_PTHREAD_STUBS
219+
value = Py_NewRef(Py_None);
220+
#elif defined(_POSIX_THREADS)
212221
#ifdef USE_SEMAPHORES
213222
value = PyUnicode_FromString("semaphore");
214223
#else
@@ -219,8 +228,7 @@ PyThread_GetInfo(void)
219228
return NULL;
220229
}
221230
#else
222-
Py_INCREF(Py_None);
223-
value = Py_None;
231+
value = Py_NewRef(Py_None);
224232
#endif
225233
PyStructSequence_SET_ITEM(threadinfo, pos++, value);
226234

Python/thread_pthread.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#if defined(__APPLE__) || defined(HAVE_PTHREAD_DESTRUCTOR)
88
#define destructor xxdestructor
99
#endif
10-
#include <pthread.h>
10+
#ifndef HAVE_PTHREAD_STUBS
11+
# include <pthread.h>
12+
#endif
1113
#if defined(__APPLE__) || defined(HAVE_PTHREAD_DESTRUCTOR)
1214
#undef destructor
1315
#endif

Python/thread_pthread_stubs.h

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#include "cpython/pthread_stubs.h"
2+
3+
// mutex
4+
int
5+
pthread_mutex_init(pthread_mutex_t *restrict mutex,
6+
const pthread_mutexattr_t *restrict attr)
7+
{
8+
return 0;
9+
}
10+
11+
int
12+
pthread_mutex_destroy(pthread_mutex_t *mutex)
13+
{
14+
return 0;
15+
}
16+
17+
int
18+
pthread_mutex_trylock(pthread_mutex_t *mutex)
19+
{
20+
return 0;
21+
}
22+
23+
int
24+
pthread_mutex_lock(pthread_mutex_t *mutex)
25+
{
26+
return 0;
27+
}
28+
29+
int
30+
pthread_mutex_unlock(pthread_mutex_t *mutex)
31+
{
32+
return 0;
33+
}
34+
35+
// condition
36+
int
37+
pthread_cond_init(pthread_cond_t *restrict cond,
38+
const pthread_condattr_t *restrict attr)
39+
{
40+
return 0;
41+
}
42+
43+
PyAPI_FUNC(int)pthread_cond_destroy(pthread_cond_t *cond)
44+
{
45+
return 0;
46+
}
47+
48+
int
49+
pthread_cond_wait(pthread_cond_t *restrict cond,
50+
pthread_mutex_t *restrict mutex)
51+
{
52+
return 0;
53+
}
54+
55+
int
56+
pthread_cond_timedwait(pthread_cond_t *restrict cond,
57+
pthread_mutex_t *restrict mutex,
58+
const struct timespec *restrict abstime)
59+
{
60+
return 0;
61+
}
62+
63+
int
64+
pthread_cond_signal(pthread_cond_t *cond)
65+
{
66+
return 0;
67+
}
68+
69+
int
70+
pthread_condattr_init(pthread_condattr_t *attr)
71+
{
72+
return 0;
73+
}
74+
75+
int
76+
pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id)
77+
{
78+
return 0;
79+
}
80+
81+
// pthread
82+
int
83+
pthread_create(pthread_t *restrict thread,
84+
const pthread_attr_t *restrict attr,
85+
void *(*start_routine)(void *),
86+
void *restrict arg)
87+
{
88+
return EAGAIN;
89+
}
90+
91+
int
92+
pthread_detach(pthread_t thread)
93+
{
94+
return 0;
95+
}
96+
97+
PyAPI_FUNC(pthread_t) pthread_self(void)
98+
{
99+
return 0;
100+
}
101+
102+
int
103+
pthread_exit(void *retval)
104+
{
105+
exit(0);
106+
}
107+
108+
int
109+
pthread_attr_init(pthread_attr_t *attr)
110+
{
111+
return 0;
112+
}
113+
114+
int
115+
pthread_attr_setstacksize(
116+
pthread_attr_t *attr, size_t stacksize)
117+
{
118+
return 0;
119+
}
120+
121+
int
122+
pthread_attr_destroy(pthread_attr_t *attr)
123+
{
124+
return 0;
125+
}
126+
127+
// pthread_key
128+
typedef struct {
129+
bool in_use;
130+
void *value;
131+
} py_tls_entry;
132+
133+
static py_tls_entry py_tls_entries[PTHREAD_KEYS_MAX] = {0};
134+
135+
int
136+
pthread_key_create(pthread_key_t *key, void (*destr_function)(void *))
137+
{
138+
if (!key) {
139+
return EINVAL;
140+
}
141+
if (destr_function != NULL) {
142+
Py_FatalError("pthread_key_create destructor is not supported");
143+
}
144+
for (pthread_key_t idx = 0; idx < PTHREAD_KEYS_MAX; idx++) {
145+
if (!py_tls_entries[idx].in_use) {
146+
py_tls_entries[idx].in_use = true;
147+
*key = idx;
148+
return 0;
149+
}
150+
}
151+
return EAGAIN;
152+
}
153+
154+
int
155+
pthread_key_delete(pthread_key_t key)
156+
{
157+
if (key < 0 || key >= PTHREAD_KEYS_MAX || !py_tls_entries[key].in_use) {
158+
return EINVAL;
159+
}
160+
py_tls_entries[key].in_use = false;
161+
py_tls_entries[key].value = NULL;
162+
return 0;
163+
}
164+
165+
166+
void *
167+
pthread_getspecific(pthread_key_t key) {
168+
if (key < 0 || key >= PTHREAD_KEYS_MAX || !py_tls_entries[key].in_use) {
169+
return NULL;
170+
}
171+
return py_tls_entries[key].value;
172+
}
173+
174+
int
175+
pthread_setspecific(pthread_key_t key, const void *value)
176+
{
177+
if (key < 0 || key >= PTHREAD_KEYS_MAX || !py_tls_entries[key].in_use) {
178+
return EINVAL;
179+
}
180+
py_tls_entries[key].value = (void *)value;
181+
return 0;
182+
}
183+
184+
// let thread_pthread define the Python API
185+
#include "thread_pthread.h"

0 commit comments

Comments
 (0)