python / cpython

The Python programming language
https://www.python.org
Other
62.82k stars 30.09k forks source link

ctypes array types create reference cycles #77448

Open f72d0bc0-f319-4dd9-9db4-d64615e4a426 opened 6 years ago

f72d0bc0-f319-4dd9-9db4-d64615e4a426 commented 6 years ago
BPO 33267
Nosy @amauryfa, @abalkin, @meadori, @eric-wieser, @MojoVampire

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields: ```python assignee = None closed_at = None created_at = labels = ['ctypes'] title = 'ctypes array types create reference cycles' updated_at = user = 'https://github.com/eric-wieser' ``` bugs.python.org fields: ```python activity = actor = 'josh.r' assignee = 'none' closed = False closed_date = None closer = None components = ['ctypes'] creation = creator = 'Eric.Wieser' dependencies = [] files = [] hgrepos = [] issue_num = 33267 keywords = [] message_count = 3.0 messages = ['315216', '315217', '315236'] nosy_count = 5.0 nosy_names = ['amaury.forgeotdarc', 'belopolsky', 'meador.inge', 'Eric.Wieser', 'josh.r'] pr_nums = [] priority = 'normal' resolution = None stage = None status = 'open' superseder = None type = None url = 'https://bugs.python.org/issue33267' versions = ['Python 3.5'] ```

f72d0bc0-f319-4dd9-9db4-d64615e4a426 commented 6 years ago

Discovered in https://github.com/numpy/numpy/pull/10882/files#r180813166

A reproduction:

In [1]: import ctypes

In [2]: def make_array_ctype(shape):
   ...:     import ctypes
   ...:     ct = ctypes.c_uint8
   ...:     for i in shape:
   ...:         ct = i * ct
   ...:     return ct
   ...:

# all on one line to keep ipython out of this
In [3]: gc.collect(); x = make_array_ctype((1,)); del x; gc.collect()

Using the proposed function in https://github.com/numpy/numpy/pull/10891, we get a few more details:

In [4]: from numpy.testing import assert_no_gc_cycles

In [5]: assert_no_gc_cycles(make_array_ctype, (1,))
AssertionError: Reference cycles were found when calling make_array_ctype: 7 objects were collected, of which 6 are shown below:
  tuple object with id=2822255556536:
    (<class '_ctypes.Array'>,)
  PyCArrayType object with id=2822226500408:
    <class '__main__.c_ubyte_Array_1'>
  getset_descriptor object with id=2822252062256:
    <attribute '__dict__' of 'c_ubyte_Array_1' objects>
  getset_descriptor object with id=2822252062184:
    <attribute '__weakref__' of 'c_ubyte_Array_1' objects>
  tuple object with id=2822243712440:
    (<class '__main__.c_ubyte_Array_1'>,
     <class '_ctypes.Array'>,
     <class '_ctypes._CData'>,
     <class 'object'>)
  StgDict object with id=2822226211928:
    {'__dict__': <attribute '__dict__' of 'c_ubyte_Array_1' objects>,
     '__doc__': None,
     '__module__': '__main__',
     '__weakref__': <attribute '__weakref__' of 'c_ubyte_Array_1' objects>,
     '_length_': 1,
     '_type_': <class 'ctypes.c_ubyte'>}

I suppose this isn't really a bug, but it's not clear to me why a cycle needs to be created here.

f72d0bc0-f319-4dd9-9db4-d64615e4a426 commented 6 years ago

Apologies, I missed the important part of that snippet:

In [3]: gc.collect(); x = make_array_ctype((1,)); del x; gc.collect()
Out[3]: 7
99ffcaa5-b43b-4e8e-a35e-9c890007b9cd commented 6 years ago

Pretty sure this is a problem with classes in general; classes are self-referencing, and using multiplication to create new ctypes array types is creating new classes.