Skip to content

Commit a05195e

Browse files
add tests
1 parent 504e741 commit a05195e

3 files changed

Lines changed: 129 additions & 29 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from test.support import threading_helper
2+
import unittest
3+
import ctypes
4+
import threading
5+
6+
threading_helper.requires_working_threading(module=True)
7+
8+
9+
class FreeThreadingTests(unittest.TestCase):
10+
11+
def test_PyCFuncPtr_new_race(self):
12+
# See https://github.com/python/cpython/issues/128567
13+
14+
def raw_func(x):
15+
pass
16+
17+
def race():
18+
barrier.wait()
19+
def lookup():
20+
prototype = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
21+
return prototype(raw_func)
22+
23+
ctypes_args = ()
24+
func = lookup()
25+
packed_args = (ctypes.c_void_p * len(ctypes_args))()
26+
for argNum in range(len(ctypes_args)):
27+
packed_args[argNum] = ctypes.cast(ctypes_args[argNum], ctypes.c_void_p)
28+
func(packed_args)
29+
30+
num_workers = 20
31+
32+
barrier = threading.Barrier(num_workers)
33+
34+
threads = []
35+
for _ in range(num_workers):
36+
t = threading.Thread(target=race)
37+
threads.append(t)
38+
39+
with threading_helper.start_threads(threads):
40+
pass
41+
42+
def test_Structure_creation_race(self):
43+
class POINT(ctypes.Structure):
44+
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]
45+
46+
def race():
47+
barrier.wait()
48+
return POINT(1, 2)
49+
50+
num_workers = 20
51+
52+
barrier = threading.Barrier(num_workers)
53+
54+
threads = []
55+
for _ in range(num_workers):
56+
t = threading.Thread(target=race)
57+
threads.append(t)
58+
59+
with threading_helper.start_threads(threads):
60+
pass
61+
62+
def test_stginfo_update_race(self):
63+
# See https://github.com/python/cpython/issues/128570
64+
def make_nd_memref_descriptor(rank, dtype):
65+
class MemRefDescriptor(ctypes.Structure):
66+
_fields_ = [
67+
("aligned", ctypes.POINTER(dtype)),
68+
]
69+
70+
return MemRefDescriptor
71+
72+
73+
class F32(ctypes.Structure):
74+
_fields_ = [("f32", ctypes.c_float)]
75+
76+
77+
def race():
78+
barrier.wait()
79+
ctp = F32
80+
ctypes.pointer(
81+
ctypes.pointer(make_nd_memref_descriptor(1, ctp)())
82+
)
83+
84+
85+
num_workers = 20
86+
87+
barrier = threading.Barrier(num_workers)
88+
89+
threads = []
90+
for _ in range(num_workers):
91+
t = threading.Thread(target=race)
92+
threads.append(t)
93+
94+
with threading_helper.start_threads(threads):
95+
pass

Modules/_ctypes/_ctypes.c

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ StructUnionType_init(PyObject *self, PyObject *args, PyObject *kwds, int isStruc
716716
/* copy base info */
717717
ret = PyCStgInfo_clone(info, baseinfo);
718718
if (ret >= 0) {
719-
stginfo_set_dict_final(info);
719+
assert(stginfo_get_dict_final(info) == 0);
720720
stginfo_set_dict_final(baseinfo);
721721
}
722722
STGINFO_UNLOCK2();
@@ -2152,18 +2152,7 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type,
21522152
if (!swapped_args)
21532153
return NULL;
21542154

2155-
if (st->swapped_suffix == NULL) {
2156-
#ifdef WORDS_BIGENDIAN
2157-
st->swapped_suffix = PyUnicode_InternFromString("_le");
2158-
#else
2159-
st->swapped_suffix = PyUnicode_InternFromString("_be");
2160-
#endif
2161-
}
2162-
if (st->swapped_suffix == NULL) {
2163-
Py_DECREF(swapped_args);
2164-
return NULL;
2165-
}
2166-
2155+
assert(st->swapped_suffix != NULL);
21672156
newname = PyUnicode_Concat(name, st->swapped_suffix);
21682157
if (newname == NULL) {
21692158
Py_DECREF(swapped_args);
@@ -3137,6 +3126,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info)
31373126
* access.
31383127
*/
31393128
assert (Py_REFCNT(obj) == 1);
3129+
assert(stginfo_get_dict_final(info) == 1);
31403130

31413131
if ((size_t)info->size <= sizeof(obj->b_value)) {
31423132
/* No need to call malloc, can use the default buffer */
@@ -3186,7 +3176,13 @@ PyCData_FromBaseObj(ctypes_state *st,
31863176
if (cmem == NULL) {
31873177
return NULL;
31883178
}
3189-
STGINFO_LOCK(info);
3179+
3180+
if (stginfo_get_dict_final(info) != 1) {
3181+
STGINFO_LOCK(info);
3182+
stginfo_set_dict_final(info);
3183+
STGINFO_UNLOCK();
3184+
}
3185+
31903186
assert(CDataObject_Check(st, cmem));
31913187
cmem->b_length = info->length;
31923188
cmem->b_size = info->size;
@@ -3198,15 +3194,12 @@ PyCData_FromBaseObj(ctypes_state *st,
31983194
cmem->b_index = index;
31993195
} else { /* copy contents of adr */
32003196
if (-1 == PyCData_MallocBuffer(cmem, info)) {
3201-
Py_CLEAR(cmem);
3202-
goto exit;
3197+
Py_DECREF(cmem);
3198+
return NULL;
32033199
}
32043200
memcpy(cmem->b_ptr, adr, info->size);
32053201
cmem->b_index = index;
32063202
}
3207-
exit:;
3208-
stginfo_set_dict_final(info);
3209-
STGINFO_UNLOCK();
32103203
return (PyObject *)cmem;
32113204
}
32123205

@@ -5126,12 +5119,8 @@ PyCArrayType_from_ctype(ctypes_state *st, PyObject *itemtype, Py_ssize_t length)
51265119
char name[256];
51275120
PyObject *len;
51285121

5129-
if (st->array_cache == NULL) {
5130-
st->array_cache = PyDict_New();
5131-
if (st->array_cache == NULL) {
5132-
return NULL;
5133-
}
5134-
}
5122+
assert(st->array_cache != NULL);
5123+
51355124
len = PyLong_FromSsize_t(length);
51365125
if (len == NULL)
51375126
return NULL;
@@ -6112,6 +6101,20 @@ _ctypes_mod_exec(PyObject *mod)
61126101
return -1;
61136102
}
61146103

6104+
st->array_cache = PyDict_New();
6105+
if (st->array_cache == NULL) {
6106+
return -1;
6107+
}
6108+
6109+
#ifdef WORDS_BIGENDIAN
6110+
st->swapped_suffix = PyUnicode_InternFromString("_le");
6111+
#else
6112+
st->swapped_suffix = PyUnicode_InternFromString("_be");
6113+
#endif
6114+
if (st->swapped_suffix == NULL) {
6115+
return -1;
6116+
}
6117+
61156118
if (_ctypes_add_types(mod) < 0) {
61166119
return -1;
61176120
}

Modules/_ctypes/stgdict.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
int
2424
PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
2525
{
26+
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&src_info->mutex);
27+
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&dst_info->mutex);
2628
Py_ssize_t size;
2729

2830
ctype_clear_stginfo(dst_info);
@@ -248,17 +250,17 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
248250
ctypes_state *st = get_module_state_by_def(Py_TYPE(type));
249251
StgInfo *stginfo;
250252
if (PyStgInfo_FromType(st, type, &stginfo) < 0) {
251-
goto error;
253+
return -1;
252254
}
253255
if (!stginfo) {
254256
PyErr_SetString(PyExc_TypeError,
255257
"ctypes state is not initialized");
256-
goto error;
258+
return -1;
257259
}
258260
PyObject *base = (PyObject *)((PyTypeObject *)type)->tp_base;
259261
StgInfo *baseinfo;
260262
if (PyStgInfo_FromType(st, base, &baseinfo) < 0) {
261-
goto error;
263+
return -1;
262264
}
263265

264266
/* If this structure/union is already marked final we cannot assign
@@ -267,7 +269,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
267269
if (stginfo_get_dict_final(stginfo) == 1) {/* is final ? */
268270
PyErr_SetString(PyExc_AttributeError,
269271
"_fields_ is final");
270-
goto error;
272+
return -1;
271273
}
272274
STGINFO_LOCK(stginfo);
273275
// check again after locking

0 commit comments

Comments
 (0)