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):
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) {
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;
}
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]