mirror of
https://github.com/python/cpython.git
synced 2025-12-11 11:31:05 +00:00
Issue #3680: Reference cycles created through a dict, set or deque iterator did not get collected.
This commit is contained in:
parent
4ba9f412bf
commit
aa687902f2
7 changed files with 96 additions and 23 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import unittest
|
import unittest
|
||||||
from test import test_support, seq_tests
|
from test import test_support, seq_tests
|
||||||
from weakref import proxy
|
import gc
|
||||||
|
import weakref
|
||||||
import copy
|
import copy
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
import random
|
import random
|
||||||
|
|
@ -418,6 +419,22 @@ class TestBasic(unittest.TestCase):
|
||||||
d.append(1)
|
d.append(1)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
def test_container_iterator(self):
|
||||||
|
# Bug # XXX: tp_traverse was not implemented for deque iterator objects
|
||||||
|
class C(object):
|
||||||
|
pass
|
||||||
|
for i in range(2):
|
||||||
|
obj = C()
|
||||||
|
ref = weakref.ref(obj)
|
||||||
|
if i == 0:
|
||||||
|
container = deque([obj, 1])
|
||||||
|
else:
|
||||||
|
container = reversed(deque([obj, 1]))
|
||||||
|
obj.x = iter(container)
|
||||||
|
del obj, container
|
||||||
|
gc.collect()
|
||||||
|
self.assert_(ref() is None, "Cycle was not collected")
|
||||||
|
|
||||||
class TestVariousIteratorArgs(unittest.TestCase):
|
class TestVariousIteratorArgs(unittest.TestCase):
|
||||||
|
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
|
|
@ -528,7 +545,7 @@ class TestSubclass(unittest.TestCase):
|
||||||
|
|
||||||
def test_weakref(self):
|
def test_weakref(self):
|
||||||
d = deque('gallahad')
|
d = deque('gallahad')
|
||||||
p = proxy(d)
|
p = weakref.proxy(d)
|
||||||
self.assertEqual(str(p), str(d))
|
self.assertEqual(str(p), str(d))
|
||||||
d = None
|
d = None
|
||||||
self.assertRaises(ReferenceError, str, p)
|
self.assertRaises(ReferenceError, str, p)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import unittest
|
||||||
from test import test_support
|
from test import test_support
|
||||||
|
|
||||||
import UserDict, random, string
|
import UserDict, random, string
|
||||||
|
import gc, weakref
|
||||||
|
|
||||||
|
|
||||||
class DictTest(unittest.TestCase):
|
class DictTest(unittest.TestCase):
|
||||||
|
|
@ -554,6 +555,19 @@ class DictTest(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
d = {}
|
d = {}
|
||||||
|
|
||||||
|
def test_container_iterator(self):
|
||||||
|
# Bug # XXX: tp_traverse was not implemented for dictiter objects
|
||||||
|
class C(object):
|
||||||
|
pass
|
||||||
|
iterators = (dict.iteritems, dict.itervalues, dict.iterkeys)
|
||||||
|
for i in iterators:
|
||||||
|
obj = C()
|
||||||
|
ref = weakref.ref(obj)
|
||||||
|
container = {obj: 1}
|
||||||
|
obj.x = i(container)
|
||||||
|
del obj, container
|
||||||
|
gc.collect()
|
||||||
|
self.assert_(ref() is None, "Cycle was not collected")
|
||||||
|
|
||||||
|
|
||||||
from test import mapping_tests
|
from test import mapping_tests
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
from test import test_support
|
from test import test_support
|
||||||
from weakref import proxy
|
import gc
|
||||||
|
import weakref
|
||||||
import operator
|
import operator
|
||||||
import copy
|
import copy
|
||||||
import pickle
|
import pickle
|
||||||
|
|
@ -322,6 +323,18 @@ class TestJointOps(unittest.TestCase):
|
||||||
self.assertEqual(sum(elem.hash_count for elem in d), n)
|
self.assertEqual(sum(elem.hash_count for elem in d), n)
|
||||||
self.assertEqual(d3, dict.fromkeys(d, 123))
|
self.assertEqual(d3, dict.fromkeys(d, 123))
|
||||||
|
|
||||||
|
def test_container_iterator(self):
|
||||||
|
# Bug # XXX: tp_traverse was not implemented for set iterator object
|
||||||
|
class C(object):
|
||||||
|
pass
|
||||||
|
obj = C()
|
||||||
|
ref = weakref.ref(obj)
|
||||||
|
container = set([obj, 1])
|
||||||
|
obj.x = iter(container)
|
||||||
|
del obj, container
|
||||||
|
gc.collect()
|
||||||
|
self.assert_(ref() is None, "Cycle was not collected")
|
||||||
|
|
||||||
class TestSet(TestJointOps):
|
class TestSet(TestJointOps):
|
||||||
thetype = set
|
thetype = set
|
||||||
|
|
||||||
|
|
@ -538,7 +551,7 @@ class TestSet(TestJointOps):
|
||||||
|
|
||||||
def test_weakref(self):
|
def test_weakref(self):
|
||||||
s = self.thetype('gallahad')
|
s = self.thetype('gallahad')
|
||||||
p = proxy(s)
|
p = weakref.proxy(s)
|
||||||
self.assertEqual(str(p), str(s))
|
self.assertEqual(str(p), str(s))
|
||||||
s = None
|
s = None
|
||||||
self.assertRaises(ReferenceError, str, p)
|
self.assertRaises(ReferenceError, str, p)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #3680: Reference cycles created through a dict, set or deque iterator
|
||||||
|
did not get collected.
|
||||||
|
|
||||||
- Issue #4701: PyObject_Hash now implicitly calls PyType_Ready on types
|
- Issue #4701: PyObject_Hash now implicitly calls PyType_Ready on types
|
||||||
where the tp_hash and tp_dict slots are both NULL.
|
where the tp_hash and tp_dict slots are both NULL.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -958,7 +958,7 @@ deque_iter(dequeobject *deque)
|
||||||
{
|
{
|
||||||
dequeiterobject *it;
|
dequeiterobject *it;
|
||||||
|
|
||||||
it = PyObject_New(dequeiterobject, &dequeiter_type);
|
it = PyObject_GC_New(dequeiterobject, &dequeiter_type);
|
||||||
if (it == NULL)
|
if (it == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
it->b = deque->leftblock;
|
it->b = deque->leftblock;
|
||||||
|
|
@ -967,14 +967,22 @@ deque_iter(dequeobject *deque)
|
||||||
it->deque = deque;
|
it->deque = deque;
|
||||||
it->state = deque->state;
|
it->state = deque->state;
|
||||||
it->counter = deque->len;
|
it->counter = deque->len;
|
||||||
|
_PyObject_GC_TRACK(it);
|
||||||
return (PyObject *)it;
|
return (PyObject *)it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dequeiter_traverse(dequeiterobject *dio, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(dio->deque);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dequeiter_dealloc(dequeiterobject *dio)
|
dequeiter_dealloc(dequeiterobject *dio)
|
||||||
{
|
{
|
||||||
Py_XDECREF(dio->deque);
|
Py_XDECREF(dio->deque);
|
||||||
Py_TYPE(dio)->tp_free(dio);
|
PyObject_GC_Del(dio);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -1039,9 +1047,9 @@ static PyTypeObject dequeiter_type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)dequeiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
@ -1060,7 +1068,7 @@ deque_reviter(dequeobject *deque)
|
||||||
{
|
{
|
||||||
dequeiterobject *it;
|
dequeiterobject *it;
|
||||||
|
|
||||||
it = PyObject_New(dequeiterobject, &dequereviter_type);
|
it = PyObject_GC_New(dequeiterobject, &dequereviter_type);
|
||||||
if (it == NULL)
|
if (it == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
it->b = deque->rightblock;
|
it->b = deque->rightblock;
|
||||||
|
|
@ -1069,6 +1077,7 @@ deque_reviter(dequeobject *deque)
|
||||||
it->deque = deque;
|
it->deque = deque;
|
||||||
it->state = deque->state;
|
it->state = deque->state;
|
||||||
it->counter = deque->len;
|
it->counter = deque->len;
|
||||||
|
_PyObject_GC_TRACK(it);
|
||||||
return (PyObject *)it;
|
return (PyObject *)it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1121,9 +1130,9 @@ static PyTypeObject dequereviter_type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)dequeiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
|
||||||
|
|
@ -2331,7 +2331,7 @@ static PyObject *
|
||||||
dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
|
dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
|
||||||
{
|
{
|
||||||
dictiterobject *di;
|
dictiterobject *di;
|
||||||
di = PyObject_New(dictiterobject, itertype);
|
di = PyObject_GC_New(dictiterobject, itertype);
|
||||||
if (di == NULL)
|
if (di == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_INCREF(dict);
|
Py_INCREF(dict);
|
||||||
|
|
@ -2348,6 +2348,7 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
di->di_result = NULL;
|
di->di_result = NULL;
|
||||||
|
_PyObject_GC_TRACK(di);
|
||||||
return (PyObject *)di;
|
return (PyObject *)di;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2356,7 +2357,15 @@ dictiter_dealloc(dictiterobject *di)
|
||||||
{
|
{
|
||||||
Py_XDECREF(di->di_dict);
|
Py_XDECREF(di->di_dict);
|
||||||
Py_XDECREF(di->di_result);
|
Py_XDECREF(di->di_result);
|
||||||
PyObject_Del(di);
|
PyObject_GC_Del(di);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dictiter_traverse(dictiterobject *di, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(di->di_dict);
|
||||||
|
Py_VISIT(di->di_result);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -2435,9 +2444,9 @@ PyTypeObject PyDictIterKey_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)dictiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
@ -2507,9 +2516,9 @@ PyTypeObject PyDictIterValue_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)dictiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
@ -2593,9 +2602,9 @@ PyTypeObject PyDictIterItem_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)dictiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
|
||||||
|
|
@ -810,7 +810,14 @@ static void
|
||||||
setiter_dealloc(setiterobject *si)
|
setiter_dealloc(setiterobject *si)
|
||||||
{
|
{
|
||||||
Py_XDECREF(si->si_set);
|
Py_XDECREF(si->si_set);
|
||||||
PyObject_Del(si);
|
PyObject_GC_Del(si);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
setiter_traverse(setiterobject *si, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(si->si_set);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
@ -888,9 +895,9 @@ static PyTypeObject PySetIter_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)setiter_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
|
|
@ -903,7 +910,7 @@ static PyTypeObject PySetIter_Type = {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
set_iter(PySetObject *so)
|
set_iter(PySetObject *so)
|
||||||
{
|
{
|
||||||
setiterobject *si = PyObject_New(setiterobject, &PySetIter_Type);
|
setiterobject *si = PyObject_GC_New(setiterobject, &PySetIter_Type);
|
||||||
if (si == NULL)
|
if (si == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_INCREF(so);
|
Py_INCREF(so);
|
||||||
|
|
@ -911,6 +918,7 @@ set_iter(PySetObject *so)
|
||||||
si->si_used = so->used;
|
si->si_used = so->used;
|
||||||
si->si_pos = 0;
|
si->si_pos = 0;
|
||||||
si->len = so->used;
|
si->len = so->used;
|
||||||
|
_PyObject_GC_TRACK(si);
|
||||||
return (PyObject *)si;
|
return (PyObject *)si;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue