mirror of
https://github.com/AdaCore/cpython.git
synced 2026-02-12 12:57:15 -08:00
bpo-39492: Fix a reference cycle between reducer_override and a Pickler instance (GH-18266)
This also needs a backport to 3.8 https://bugs.python.org/issue39492 Automerge-Triggered-By: @pitrou
This commit is contained in:
@@ -3499,6 +3499,30 @@ class AbstractHookTests(unittest.TestCase):
|
|||||||
ValueError, 'The reducer just failed'):
|
ValueError, 'The reducer just failed'):
|
||||||
p.dump(h)
|
p.dump(h)
|
||||||
|
|
||||||
|
@support.cpython_only
|
||||||
|
def test_reducer_override_no_reference_cycle(self):
|
||||||
|
# bpo-39492: reducer_override used to induce a spurious reference cycle
|
||||||
|
# inside the Pickler object, that could prevent all serialized objects
|
||||||
|
# from being garbage-collected without explicity invoking gc.collect.
|
||||||
|
|
||||||
|
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||||
|
with self.subTest(proto=proto):
|
||||||
|
def f():
|
||||||
|
pass
|
||||||
|
|
||||||
|
wr = weakref.ref(f)
|
||||||
|
|
||||||
|
bio = io.BytesIO()
|
||||||
|
p = self.pickler_class(bio, proto)
|
||||||
|
p.dump(f)
|
||||||
|
new_f = pickle.loads(bio.getvalue())
|
||||||
|
assert new_f == 5
|
||||||
|
|
||||||
|
del p
|
||||||
|
del f
|
||||||
|
|
||||||
|
self.assertIsNone(wr())
|
||||||
|
|
||||||
|
|
||||||
class AbstractDispatchTableTests(unittest.TestCase):
|
class AbstractDispatchTableTests(unittest.TestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Fix a reference cycle in the C Pickler that was preventing the garbage collection of deleted, pickled objects.
|
||||||
@@ -4455,12 +4455,13 @@ static int
|
|||||||
dump(PicklerObject *self, PyObject *obj)
|
dump(PicklerObject *self, PyObject *obj)
|
||||||
{
|
{
|
||||||
const char stop_op = STOP;
|
const char stop_op = STOP;
|
||||||
|
int status = -1;
|
||||||
PyObject *tmp;
|
PyObject *tmp;
|
||||||
_Py_IDENTIFIER(reducer_override);
|
_Py_IDENTIFIER(reducer_override);
|
||||||
|
|
||||||
if (_PyObject_LookupAttrId((PyObject *)self, &PyId_reducer_override,
|
if (_PyObject_LookupAttrId((PyObject *)self, &PyId_reducer_override,
|
||||||
&tmp) < 0) {
|
&tmp) < 0) {
|
||||||
return -1;
|
goto error;
|
||||||
}
|
}
|
||||||
/* Cache the reducer_override method, if it exists. */
|
/* Cache the reducer_override method, if it exists. */
|
||||||
if (tmp != NULL) {
|
if (tmp != NULL) {
|
||||||
@@ -4477,7 +4478,7 @@ dump(PicklerObject *self, PyObject *obj)
|
|||||||
assert(self->proto >= 0 && self->proto < 256);
|
assert(self->proto >= 0 && self->proto < 256);
|
||||||
header[1] = (unsigned char)self->proto;
|
header[1] = (unsigned char)self->proto;
|
||||||
if (_Pickler_Write(self, header, 2) < 0)
|
if (_Pickler_Write(self, header, 2) < 0)
|
||||||
return -1;
|
goto error;
|
||||||
if (self->proto >= 4)
|
if (self->proto >= 4)
|
||||||
self->framing = 1;
|
self->framing = 1;
|
||||||
}
|
}
|
||||||
@@ -4485,9 +4486,22 @@ dump(PicklerObject *self, PyObject *obj)
|
|||||||
if (save(self, obj, 0) < 0 ||
|
if (save(self, obj, 0) < 0 ||
|
||||||
_Pickler_Write(self, &stop_op, 1) < 0 ||
|
_Pickler_Write(self, &stop_op, 1) < 0 ||
|
||||||
_Pickler_CommitFrame(self) < 0)
|
_Pickler_CommitFrame(self) < 0)
|
||||||
return -1;
|
goto error;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
error:
|
||||||
self->framing = 0;
|
self->framing = 0;
|
||||||
return 0;
|
|
||||||
|
/* Break the reference cycle we generated at the beginning this function
|
||||||
|
* call when setting the reducer_override attribute of the Pickler instance
|
||||||
|
* to a bound method of the same instance. This is important as the Pickler
|
||||||
|
* instance holds a reference to each object it has pickled (through its
|
||||||
|
* memo): thus, these objects wont be garbage-collected as long as the
|
||||||
|
* Pickler itself is not collected. */
|
||||||
|
Py_CLEAR(self->reducer_override);
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
|||||||
Reference in New Issue
Block a user