https://github.com/python/cpython/commit/3b1722ff3ced17db57ea724dbe56f6c7b907682d
commit: 3b1722ff3ced17db57ea724dbe56f6c7b907682d
branch: main
author: John <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-11-04T08:43:39-05:00
summary:
gh-159558: Avoid re-entering an already held critical section in ctypes setters
(GH-159562)
files:
A Misc/NEWS.d/next/Library/2026-10-16-18-23-03.gh-issue-159558.ctRelk.rst
M Modules/_ctypes/_ctypes.c
diff --git
a/Misc/NEWS.d/next/Library/2026-10-16-18-23-03.gh-issue-159558.ctRelk.rst
b/Misc/NEWS.d/next/Library/2026-10-16-18-23-03.gh-issue-159558.ctRelk.rst
new file mode 100644
index 000000000000000..a8b8570365f3f48
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-16-18-23-03.gh-issue-159558.ctRelk.rst
@@ -0,0 +1,4 @@
+Speed up assignment to the ``value`` attribute of simple :mod:`ctypes` types,
to
+the ``contents`` attribute of :mod:`ctypes` pointers and to :mod:`ctypes`
+structure fields, array items and pointer items on the free-threaded build by
+not re-entering the critical section already held on the object.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 1035c91005759df..58c6e6fde27eec1 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -2888,28 +2888,33 @@ static PyType_Spec pycfuncptr_type_spec = {
* Code to keep needed objects alive
*/
+static CDataObject *
+PyCData_GetContainer_lock_held(CDataObject *root)
+{
+ assert(root->b_base == NULL);
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(root);
+ if (root->b_objects == NULL) {
+ if (root->b_length) {
+ root->b_objects = PyDict_New();
+ if (root->b_objects == NULL) {
+ return NULL;
+ }
+ } else {
+ root->b_objects = Py_NewRef(Py_None);
+ }
+ }
+ return root;
+}
+
static CDataObject *
PyCData_GetContainer(CDataObject *self)
{
while (self->b_base) {
self = self->b_base;
}
- CDataObject *res = self;
+ CDataObject *res;
Py_BEGIN_CRITICAL_SECTION(self);
- // avoid using return directly in this block because critical section
- // needs to be released before returning
- if (self->b_objects == NULL) {
- if (self->b_length) {
- self->b_objects = PyDict_New();
- if (self->b_objects == NULL) {
- res = NULL;
- goto exit;
- }
- } else {
- self->b_objects = Py_NewRef(Py_None);
- }
- }
-exit:;
+ res = PyCData_GetContainer_lock_held(self);
Py_END_CRITICAL_SECTION();
return res;
}
@@ -2924,6 +2929,22 @@ GetKeepedObjects(CDataObject *target)
return container->b_objects;
}
+/* Like GetKeepedObjects(), for a caller that already holds target's lock. */
+static PyObject *
+GetKeepedObjects_lock_held(CDataObject *target)
+{
+ CDataObject *container;
+ if (target->b_base == NULL) {
+ container = PyCData_GetContainer_lock_held(target);
+ }
+ else {
+ container = PyCData_GetContainer(target);
+ }
+ if (container == NULL)
+ return NULL;
+ return container->b_objects;
+}
+
static PyObject *
unique_key(CDataObject *target, Py_ssize_t index)
{
@@ -2960,7 +2981,15 @@ KeepRef_lock_held(CDataObject *target, Py_ssize_t index,
PyObject *keep)
Py_DECREF(Py_None);
return 0;
}
- ob = PyCData_GetContainer(target);
+ if (target->b_base == NULL) {
+ // target is its own root and the caller already holds its lock:
+ // re-entering the critical section would be a nested same-object
+ // lock (failed CAS + slow path) on every field assignment.
+ ob = PyCData_GetContainer_lock_held(target);
+ }
+ else {
+ ob = PyCData_GetContainer(target);
+ }
if (ob == NULL) {
Py_DECREF(keep);
return -1;
@@ -3564,7 +3593,7 @@ PyCData_set(ctypes_state *st,
/* KeepRef steals a refcount from it's last argument */
/* If KeepRef fails, we are stumped. The dst memory block has already
been changed */
- return KeepRef(mem, index, result);
+ return KeepRef_lock_held(mem, index, result);
}
@@ -5452,8 +5481,9 @@ _ctypes_Simple_value_set_impl(CDataObject *self, PyObject
*value)
if (!result)
return -1;
- /* consumes the refcount the setfunc returns */
- return KeepRef(self, 0, result);
+ /* consumes the refcount the setfunc returns; self is already locked
+ by the critical section taken by the Argument Clinic wrapper */
+ return KeepRef_lock_held(self, 0, result);
}
@@ -5763,15 +5793,15 @@ Pointer_set_contents_lock_held(PyObject *op, PyObject
*value, void *closure)
'value' itself as the second item of the b_objects list, additionally.
*/
Py_INCREF(value);
- if (-1 == KeepRef(self, 1, value))
+ if (-1 == KeepRef_lock_held(self, 1, value))
return -1;
- keep = GetKeepedObjects(dst);
+ keep = GetKeepedObjects_lock_held(dst);
if (keep == NULL)
return -1;
Py_INCREF(keep);
- return KeepRef(self, 0, keep);
+ return KeepRef_lock_held(self, 0, keep);
}
static int
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]