Skip to content

Commit 3b1722f

Browse files
authored
gh-159558: Avoid re-entering an already held critical section in ctypes setters (GH-159562)
1 parent b301c29 commit 3b1722f

2 files changed

Lines changed: 56 additions & 22 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Speed up assignment to the ``value`` attribute of simple :mod:`ctypes` types, to
2+
the ``contents`` attribute of :mod:`ctypes` pointers and to :mod:`ctypes`
3+
structure fields, array items and pointer items on the free-threaded build by
4+
not re-entering the critical section already held on the object.

‎Modules/_ctypes/_ctypes.c‎

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,28 +2888,33 @@ static PyType_Spec pycfuncptr_type_spec = {
28882888
* Code to keep needed objects alive
28892889
*/
28902890

2891+
static CDataObject *
2892+
PyCData_GetContainer_lock_held(CDataObject *root)
2893+
{
2894+
assert(root->b_base == NULL);
2895+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(root);
2896+
if (root->b_objects == NULL) {
2897+
if (root->b_length) {
2898+
root->b_objects = PyDict_New();
2899+
if (root->b_objects == NULL) {
2900+
return NULL;
2901+
}
2902+
} else {
2903+
root->b_objects = Py_NewRef(Py_None);
2904+
}
2905+
}
2906+
return root;
2907+
}
2908+
28912909
static CDataObject *
28922910
PyCData_GetContainer(CDataObject *self)
28932911
{
28942912
while (self->b_base) {
28952913
self = self->b_base;
28962914
}
2897-
CDataObject *res = self;
2915+
CDataObject *res;
28982916
Py_BEGIN_CRITICAL_SECTION(self);
2899-
// avoid using return directly in this block because critical section
2900-
// needs to be released before returning
2901-
if (self->b_objects == NULL) {
2902-
if (self->b_length) {
2903-
self->b_objects = PyDict_New();
2904-
if (self->b_objects == NULL) {
2905-
res = NULL;
2906-
goto exit;
2907-
}
2908-
} else {
2909-
self->b_objects = Py_NewRef(Py_None);
2910-
}
2911-
}
2912-
exit:;
2917+
res = PyCData_GetContainer_lock_held(self);
29132918
Py_END_CRITICAL_SECTION();
29142919
return res;
29152920
}
@@ -2924,6 +2929,22 @@ GetKeepedObjects(CDataObject *target)
29242929
return container->b_objects;
29252930
}
29262931

2932+
/* Like GetKeepedObjects(), for a caller that already holds target's lock. */
2933+
static PyObject *
2934+
GetKeepedObjects_lock_held(CDataObject *target)
2935+
{
2936+
CDataObject *container;
2937+
if (target->b_base == NULL) {
2938+
container = PyCData_GetContainer_lock_held(target);
2939+
}
2940+
else {
2941+
container = PyCData_GetContainer(target);
2942+
}
2943+
if (container == NULL)
2944+
return NULL;
2945+
return container->b_objects;
2946+
}
2947+
29272948
static PyObject *
29282949
unique_key(CDataObject *target, Py_ssize_t index)
29292950
{
@@ -2960,7 +2981,15 @@ KeepRef_lock_held(CDataObject *target, Py_ssize_t index, PyObject *keep)
29602981
Py_DECREF(Py_None);
29612982
return 0;
29622983
}
2963-
ob = PyCData_GetContainer(target);
2984+
if (target->b_base == NULL) {
2985+
// target is its own root and the caller already holds its lock:
2986+
// re-entering the critical section would be a nested same-object
2987+
// lock (failed CAS + slow path) on every field assignment.
2988+
ob = PyCData_GetContainer_lock_held(target);
2989+
}
2990+
else {
2991+
ob = PyCData_GetContainer(target);
2992+
}
29642993
if (ob == NULL) {
29652994
Py_DECREF(keep);
29662995
return -1;
@@ -3564,7 +3593,7 @@ PyCData_set(ctypes_state *st,
35643593
/* KeepRef steals a refcount from it's last argument */
35653594
/* If KeepRef fails, we are stumped. The dst memory block has already
35663595
been changed */
3567-
return KeepRef(mem, index, result);
3596+
return KeepRef_lock_held(mem, index, result);
35683597
}
35693598

35703599

@@ -5452,8 +5481,9 @@ _ctypes_Simple_value_set_impl(CDataObject *self, PyObject *value)
54525481
if (!result)
54535482
return -1;
54545483

5455-
/* consumes the refcount the setfunc returns */
5456-
return KeepRef(self, 0, result);
5484+
/* consumes the refcount the setfunc returns; self is already locked
5485+
by the critical section taken by the Argument Clinic wrapper */
5486+
return KeepRef_lock_held(self, 0, result);
54575487
}
54585488

54595489

@@ -5763,15 +5793,15 @@ Pointer_set_contents_lock_held(PyObject *op, PyObject *value, void *closure)
57635793
'value' itself as the second item of the b_objects list, additionally.
57645794
*/
57655795
Py_INCREF(value);
5766-
if (-1 == KeepRef(self, 1, value))
5796+
if (-1 == KeepRef_lock_held(self, 1, value))
57675797
return -1;
57685798

5769-
keep = GetKeepedObjects(dst);
5799+
keep = GetKeepedObjects_lock_held(dst);
57705800
if (keep == NULL)
57715801
return -1;
57725802

57735803
Py_INCREF(keep);
5774-
return KeepRef(self, 0, keep);
5804+
return KeepRef_lock_held(self, 0, keep);
57755805
}
57765806

57775807
static int

0 commit comments

Comments
 (0)