Skip to content

gh-100926: Move ctype's pointers cache to StgInfo #131282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 77 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
87a974b
Move ctype's pointers cache to StgInfo
sergey-miryanov Mar 15, 2025
12600a6
Fix PyCStgInfo_clone for pointer_type
sergey-miryanov Mar 15, 2025
2b0d69f
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 15, 2025
df2167a
No need to use _ctypes_ptrtype_cache for create_pointer_inst
sergey-miryanov Mar 15, 2025
f96ae59
Remove unused st from create_pointer_inst
sergey-miryanov Mar 15, 2025
481cf59
It is better to check local cache first
sergey-miryanov Mar 16, 2025
96b4dd1
Update Modules/_ctypes/callproc.c
sergey-miryanov Mar 17, 2025
4fb5baa
Update Modules/_ctypes/callproc.c
sergey-miryanov Mar 17, 2025
758045f
Add news and whatsnew entries
sergey-miryanov Mar 17, 2025
2f9285f
Update Modules/_ctypes/_ctypes.c
sergey-miryanov Mar 18, 2025
b070ad5
Arrange pointer_type declaration
sergey-miryanov Mar 18, 2025
ef1e633
Use assertIs to check pointer types in tests
sergey-miryanov Mar 18, 2025
6ea410d
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 21, 2025
3754d3d
Implement pointer-type creation via __pointer_type__
sergey-miryanov Mar 21, 2025
10e36e4
Fix _CType_Type
sergey-miryanov Mar 22, 2025
29bfe9c
Update Lib/test/test_ctypes/test_c_simple_type_meta.py
sergey-miryanov Mar 22, 2025
f8139ff
Fix POINTER and test_creating_pointer_in_dunder_new_1
sergey-miryanov Mar 22, 2025
e1aaf45
Do not share pointer_type via StgInfo clone
sergey-miryanov Mar 23, 2025
82f74ec
Test if PyCStgInfo_clone not share pointer_type cache
sergey-miryanov Mar 23, 2025
eacc724
Simplify POINTER
sergey-miryanov Mar 23, 2025
0b373d5
Add some extra checks and tests
sergey-miryanov Mar 23, 2025
9c49abd
Fix tests for ct_meta with init
sergey-miryanov Mar 24, 2025
3c38aa5
Do not use Py_XSETREF if dst already NULL
sergey-miryanov Mar 24, 2025
efd4961
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 24, 2025
e251a7d
Add docstrings for POINTER and pointer
sergey-miryanov Mar 24, 2025
cdba1f6
Remove import of c-versions of POINTER and pointer
sergey-miryanov Mar 24, 2025
a241cd9
Remove c-versions of POINTER and pointer
sergey-miryanov Mar 24, 2025
8d85624
Remove c-versions of _pointer_type_cache/_ctypes_ptrtype_cache
sergey-miryanov Mar 24, 2025
aedc4b2
Add extra test for set_type/PyCPointerType_SetProto
sergey-miryanov Mar 24, 2025
6ac84c7
Add test for creating types with factory
sergey-miryanov Mar 24, 2025
2373c63
Fix docstrings
sergey-miryanov Mar 25, 2025
fc93bc8
Add some tests for pointer
sergey-miryanov Mar 25, 2025
5768347
Update news and whatsnew
sergey-miryanov Mar 25, 2025
a2cff24
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 26, 2025
87f8cf3
Fix tests
sergey-miryanov Mar 26, 2025
5b9891f
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 26, 2025
85ff1a0
Add few more tests
sergey-miryanov Mar 26, 2025
d11af80
Add some more tests
sergey-miryanov Mar 26, 2025
6dd1e1a
Update docs
sergey-miryanov Mar 26, 2025
33ae038
Update news
sergey-miryanov Mar 26, 2025
08bdada
Fix news
sergey-miryanov Mar 26, 2025
8505d4b
Add some more tests
sergey-miryanov Mar 27, 2025
c1bf7cc
Add more tests for metabases pointers
sergey-miryanov Mar 30, 2025
ea0bf20
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 30, 2025
fc3bc33
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Mar 30, 2025
62d2deb
Try to add thread safety to pointer_type
sergey-miryanov Mar 30, 2025
360303f
Revert "Try to add thread safety to pointer_type"
sergey-miryanov Mar 31, 2025
a2cc961
Add set_non_ctypes_pointer_type
sergey-miryanov Apr 1, 2025
df56541
Add some new test and docstrings for tests
sergey-miryanov Apr 2, 2025
283bf96
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Apr 2, 2025
7d42666
Address some neonene comments
sergey-miryanov Apr 3, 2025
0bb389e
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov Apr 26, 2025
e31c7e9
Move __pointer_type__ docs to common class variables section
sergey-miryanov Apr 26, 2025
56d41e2
Apply suggestions from code review
sergey-miryanov Apr 30, 2025
4316ad9
Remove Pointers for ctypes-like types section
sergey-miryanov Apr 30, 2025
9c99f9c
Update news entry
sergey-miryanov Apr 30, 2025
5753fc9
Update docstring for POINTER and add comments
sergey-miryanov Apr 30, 2025
58a1507
Implement _pointer_type_cache as PointerTypeCache proxy
sergey-miryanov Apr 30, 2025
7082009
Raise AttributeError if __pointer_type__ is not set
sergey-miryanov Apr 30, 2025
f89da96
Fixed POINTER if __pointer_type__ already initialized
sergey-miryanov May 1, 2025
1afa7b3
Fixed default value for PointerTypeCache.get
sergey-miryanov May 1, 2025
e99277c
Remove setting item in _pointer_type_cache from SetPointerType
sergey-miryanov May 1, 2025
ebed23e
tests: Remove teardown code that now does nothing
encukou Apr 30, 2025
c4ee75a
Remove _pointer_type_cache setting
encukou Apr 30, 2025
66c68b0
Handle old-style incomplete types
encukou May 1, 2025
0d2c75b
Document deprecations
encukou May 1, 2025
8597f6b
Allow setting/deleting __pointer_type__
encukou May 1, 2025
0b5de27
Allow arbitrary set_type as before
encukou May 1, 2025
78b6c15
Apply suggestions from code review
sergey-miryanov May 1, 2025
dfd529c
Merge encukou seggestions from PR
sergey-miryanov May 2, 2025
2fc600b
Fix typos in test_pointer_set_wrong_type test
sergey-miryanov May 2, 2025
cb78a03
set_pointer for type should reset type cache
sergey-miryanov May 2, 2025
4ef57e9
Merge branch 'main' into gh-100926-ctypes-pointers-cache
sergey-miryanov May 2, 2025
b33890d
get and getitem should consistent
sergey-miryanov May 2, 2025
0acc2b2
Merge branch 'main' into gh-100926-ctypes-pointers-cache
encukou May 2, 2025
3129ef5
Wording tweak
encukou May 2, 2025
8efa28a
Apply suggestions from code review
encukou May 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,11 @@ ctypes
loaded by the current process.
(Contributed by Brian Ward in :gh:`119349`.)

* Move :func:`ctypes.POINTER` types cache from the global ``_pointer_type_cache``
to the corresponding :mod:`ctypes` types. This will stop the cache from
growing without limits in some situations.
(Contributed by Sergey Miryanov in :gh:`100926`).

datetime
--------

Expand Down
4 changes: 1 addition & 3 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,9 @@ def SetPointerType(pointer, cls):
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
if _pointer_type_cache.get(cls, None) is not None:
raise RuntimeError("This type already exists in the cache")
if id(pointer) not in _pointer_type_cache:
raise RuntimeError("What's this???")

pointer.set_type(cls)
_pointer_type_cache[cls] = pointer
del _pointer_type_cache[id(pointer)]

def ARRAY(typ, len):
return typ * len
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_ctypes/test_byteswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ class TestStructure(parent):
self.assertEqual(len(data), sizeof(TestStructure))
ptr = POINTER(TestStructure)
s = cast(data, ptr)[0]
del ctypes._pointer_type_cache[TestStructure]
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)

Expand Down Expand Up @@ -359,7 +358,6 @@ class TestUnion(parent):
self.assertEqual(len(data), sizeof(TestUnion))
ptr = POINTER(TestUnion)
s = cast(data, ptr)[0]
del ctypes._pointer_type_cache[TestUnion]
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)

Expand Down
7 changes: 1 addition & 6 deletions Lib/test/test_ctypes/test_keeprefs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import unittest
from ctypes import (Structure, POINTER, pointer, _pointer_type_cache,
c_char_p, c_int)
from ctypes import (Structure, POINTER, pointer, c_char_p, c_int)


class SimpleTestCase(unittest.TestCase):
Expand Down Expand Up @@ -115,10 +114,6 @@ class RECT(Structure):
r.a[0].x = 42
r.a[0].y = 99

# to avoid leaking when tests are run several times
# clean up the types left in the cache.
del _pointer_type_cache[POINT]


if __name__ == "__main__":
unittest.main()
10 changes: 1 addition & 9 deletions Lib/test/test_ctypes/test_pointers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
import unittest
from ctypes import (CDLL, CFUNCTYPE, Structure,
POINTER, pointer, _Pointer, _pointer_type_cache,
POINTER, pointer, _Pointer,
byref, sizeof,
c_void_p, c_char_p,
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
Expand Down Expand Up @@ -141,8 +141,6 @@ class Table(Structure):

pt.contents.c = 33

del _pointer_type_cache[Table]

def test_basic(self):
p = pointer(c_int(42))
# Although a pointer can be indexed, it has no length
Expand Down Expand Up @@ -210,17 +208,11 @@ def test_pointer_type_name(self):
LargeNamedType = type('T' * 2 ** 25, (Structure,), {})
self.assertTrue(POINTER(LargeNamedType))

# to not leak references, we must clean _pointer_type_cache
del _pointer_type_cache[LargeNamedType]

def test_pointer_type_str_name(self):
large_string = 'T' * 2 ** 25
P = POINTER(large_string)
self.assertTrue(P)

# to not leak references, we must clean _pointer_type_cache
del _pointer_type_cache[id(P)]

def test_abstract(self):
self.assertRaises(TypeError, _Pointer.set_type, 42)

Expand Down
3 changes: 0 additions & 3 deletions Lib/test/test_ctypes/test_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import sys
import unittest
from ctypes import (Structure, CDLL, POINTER, pythonapi,
_pointer_type_cache,
c_ubyte, c_char_p, c_int)
from test.support import import_helper

Expand Down Expand Up @@ -96,8 +95,6 @@ class struct_frozen(Structure):
"_PyImport_FrozenBootstrap example "
"in Doc/library/ctypes.rst may be out of date")

del _pointer_type_cache[struct_frozen]

def test_undefined(self):
self.assertRaises(ValueError, c_int.in_dll, pythonapi,
"Undefined_Symbol")
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/test_ctypes/test_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys
import unittest
from ctypes import (CDLL, Structure, POINTER, pointer, sizeof, byref,
_pointer_type_cache,
c_void_p, c_char, c_int, c_long)
from test import support
from test.support import import_helper
Expand Down Expand Up @@ -145,8 +144,8 @@ class RECT(Structure):
self.assertEqual(ret.top, top.value)
self.assertEqual(ret.bottom, bottom.value)

# to not leak references, we must clean _pointer_type_cache
del _pointer_type_cache[RECT]
self.assertEqual(id(PointInRect.argtypes[0]), id(ReturnRect.argtypes[2]))
self.assertEqual(id(PointInRect.argtypes[0]), id(ReturnRect.argtypes[5]))


if __name__ == '__main__':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Move :func:`ctypes.POINTER` types cache from the global ``_pointer_type_cache``
to the corresponding :mod:`ctypes` types to avoid unlimited growth of the cache.
4 changes: 3 additions & 1 deletion Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ CType_Type_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(info->restype);
Py_VISIT(info->checker);
Py_VISIT(info->module);
Py_VISIT(info->pointer_type);
}
Py_VISIT(Py_TYPE(self));
return PyType_Type.tp_traverse(self, visit, arg);
Expand All @@ -488,7 +489,8 @@ ctype_clear_stginfo(StgInfo *info)
Py_CLEAR(info->converters);
Py_CLEAR(info->restype);
Py_CLEAR(info->checker);
Py_CLEAR(info->module);
Py_CLEAR(info->pointer_type);
Py_CLEAR(info->module); // decref the module last
}

static int
Expand Down
38 changes: 18 additions & 20 deletions Modules/_ctypes/callproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1990,14 +1990,24 @@ create_pointer_type(PyObject *module, PyObject *cls)
{
PyObject *result;
PyTypeObject *typ;
PyObject *key;

assert(module);
ctypes_state *st = get_module_state(module);
StgInfo *info = NULL;
if (PyType_Check(cls)) {
if (PyStgInfo_FromType(st, cls, &info) < 0) {
return NULL;
}
if (info && info->pointer_type) {
return Py_NewRef(info->pointer_type);
}
}

if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, cls, &result) != 0) {
// found or error
return result;
}

// not found
if (PyUnicode_CheckExact(cls)) {
PyObject *name = PyUnicode_FromFormat("LP_%U", cls);
Expand All @@ -2007,11 +2017,6 @@ create_pointer_type(PyObject *module, PyObject *cls)
st->PyCPointer_Type);
if (result == NULL)
return result;
key = PyLong_FromVoidPtr(result);
if (key == NULL) {
Py_DECREF(result);
return NULL;
}
} else if (PyType_Check(cls)) {
typ = (PyTypeObject *)cls;
PyObject *name = PyUnicode_FromFormat("LP_%s", typ->tp_name);
Expand All @@ -2022,17 +2027,15 @@ create_pointer_type(PyObject *module, PyObject *cls)
"_type_", cls);
if (result == NULL)
return result;
key = Py_NewRef(cls);
} else {
PyErr_SetString(PyExc_TypeError, "must be a ctypes type");
return NULL;
}
if (PyDict_SetItem(st->_ctypes_ptrtype_cache, key, result) < 0) {
Py_DECREF(result);
Py_DECREF(key);
return NULL;

if (info) {
info->pointer_type = Py_NewRef(result);
}
Py_DECREF(key);

return result;
}

Expand All @@ -2056,15 +2059,10 @@ create_pointer_inst(PyObject *module, PyObject *arg)
PyObject *result;
PyObject *typ;

ctypes_state *st = get_module_state(module);
if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, (PyObject *)Py_TYPE(arg), &typ) < 0) {
typ = create_pointer_type(module, (PyObject *)Py_TYPE(arg));
if (typ == NULL)
return NULL;
}
if (typ == NULL) {
typ = create_pointer_type(module, (PyObject *)Py_TYPE(arg));
if (typ == NULL)
return NULL;
}

result = PyObject_CallOneArg(typ, arg);
Py_DECREF(typ);
return result;
Expand Down
2 changes: 2 additions & 0 deletions Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ typedef struct {
Py_ssize_t *shape;
/* Py_ssize_t *strides; */ /* unused in ctypes */
/* Py_ssize_t *suboffsets; */ /* unused in ctypes */
PyObject *pointer_type;
} StgInfo;

extern int PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info);
Expand Down Expand Up @@ -566,6 +567,7 @@ PyStgInfo_Init(ctypes_state *state, PyTypeObject *type)
return NULL;
}
info->module = Py_NewRef(module);
info->pointer_type = NULL;

info->initialized = 1;
return info;
Expand Down
1 change: 1 addition & 0 deletions Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
Py_XINCREF(dst_info->restype);
Py_XINCREF(dst_info->checker);
Py_XINCREF(dst_info->module);
Py_XINCREF(dst_info->pointer_type);

if (src_info->format) {
dst_info->format = PyMem_Malloc(strlen(src_info->format) + 1);
Expand Down
Loading