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:
Pierre Glaser 2020-02-02 19:55:21 +01:00 committed by GitHub
parent 339fd46cb7
commit 0f2f35e15f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 4 deletions

View File

@ -3499,6 +3499,30 @@ class AbstractHookTests(unittest.TestCase):
ValueError, 'The reducer just failed'):
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):

View File

@ -0,0 +1 @@
Fix a reference cycle in the C Pickler that was preventing the garbage collection of deleted, pickled objects.

View File

@ -4455,12 +4455,13 @@ static int
dump(PicklerObject *self, PyObject *obj)
{
const char stop_op = STOP;
int status = -1;
PyObject *tmp;
_Py_IDENTIFIER(reducer_override);
if (_PyObject_LookupAttrId((PyObject *)self, &PyId_reducer_override,
&tmp) < 0) {
return -1;
goto error;
}
/* Cache the reducer_override method, if it exists. */
if (tmp != NULL) {
@ -4477,7 +4478,7 @@ dump(PicklerObject *self, PyObject *obj)
assert(self->proto >= 0 && self->proto < 256);
header[1] = (unsigned char)self->proto;
if (_Pickler_Write(self, header, 2) < 0)
return -1;
goto error;
if (self->proto >= 4)
self->framing = 1;
}
@ -4485,9 +4486,22 @@ dump(PicklerObject *self, PyObject *obj)
if (save(self, obj, 0) < 0 ||
_Pickler_Write(self, &stop_op, 1) < 0 ||
_Pickler_CommitFrame(self) < 0)
return -1;
goto error;
// Success
status = 0;
error:
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]