You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Infinite recursion in Pickler.persistent_id #89850
(gdb) p _PyObject_Dump(self->pers_func)
object address : 0x7ffff74b90d0
object refcount : 3
object type: 0x555555a6f020
object type name: function
object repr :<function Pickler.persistent_id at 0x7ffff74b90d0>$1 = void
(gdb) c
Continuing.
Breakpoint 1, Pickler_get_persid (self=0x7ffff75c13e0, _unused_ignored=0x0) at ./Modules/_pickle.c:5004
5004 if (self->pers_func == NULL) {
(gdb) p _PyObject_Dump(self->pers_func)
object address : 0x7ffff74b90d0
object refcount : 4
object type: 0x555555a6f020
object type name: function
object repr :<function Pickler.persistent_id at 0x7ffff74b90d0>$2 = void
(gdb) c
Continuing.
Breakpoint 1, Pickler_get_persid (self=0x7ffff75c13e0, _unused_ignored=0x0) at ./Modules/_pickle.c:5004
5004 if (self->pers_func == NULL) {
(gdb) p _PyObject_Dump(self->pers_func)
object address : 0x7ffff74b90d0
object refcount : 5
object type: 0x555555a6f020
object type name: function
object repr :<function Pickler.persistent_id at 0x7ffff74b90d0>$3 = void
(gdb)
The refcount of self->pers_func appears to be increasing which causes the recursion error. I tried to decrease the refcount (I reverted the changes for this function on this PR, by the way, I didn't see any reference cycles @serhiy-storchaka ) Recursion error is fixed but I am getting TypeError
The C implementation saves references to persistent_id and persistent_load methods for performance reason. It saves a method lookup and creation. Also, since the C implementation of Pickler does not have the persistent_id method by default, it allows to avoid calling the trivial method at all.
Since saving a reference to a method creates a reference loop, it uses a trick: deconstruct it if it is a bound method and save only reference to an unbound method. When read the persistent_id attribute, the bound method is reconstructed back.
But it does not work with super(). super() simply skips the current class when looks up the attribute in the MRO. But in this case it cames to the persistent_id descriptor in the base class which returns a bound method created from an unbound method of the subclass. So calling it leads to an infinite recursion.
There is the same issue with persistent_load method.
PR #113579 uses a different approach, similar to the one used for reducer_override. It removes persistent_id and persistent_load descriptors and caches the bound methods, but only for the time of execution dump() and load() methods. It also adds the default implementations of persistent_id and persistent_load methods in the C classes as well as in the Python classes.
This approach may have disadvantage: calling dump() and load() for small objects may now be slower due to added constant overhead. But I hope that the overhead is insignificant in comparison with other operations (IO, memory management).
Activity
embe commentedon Nov 2, 2021
The following code, which seems reasonable:
crashes with:
RecursionError: maximum recursion depth exceeded while calling a Python object
It works perfectly when inheriting from pickle._Pickler (the Python implementation).
4 remaining items
iritkatriel commentedon Jun 1, 2022
I think it's because in the C version persistent_id can be NULL (but it's still in dir()):
on the other hand:
furkanonder commentedon Dec 20, 2022
The refcount of
self->pers_funcappears to be increasing which causes the recursion error. I tried to decrease the refcount (I reverted the changes for this function on this PR, by the way, I didn't see any reference cycles @serhiy-storchaka ) Recursion error is fixed but I am gettingTypeErrorpythongh-89850: Add default C implementations of persistent_id() and …
serhiy-storchaka commentedon Dec 30, 2023
The C implementation saves references to
persistent_idandpersistent_loadmethods for performance reason. It saves a method lookup and creation. Also, since the C implementation ofPicklerdoes not have thepersistent_idmethod by default, it allows to avoid calling the trivial method at all.Since saving a reference to a method creates a reference loop, it uses a trick: deconstruct it if it is a bound method and save only reference to an unbound method. When read the
persistent_idattribute, the bound method is reconstructed back.But it does not work with
super().super()simply skips the current class when looks up the attribute in the MRO. But in this case it cames to thepersistent_iddescriptor in the base class which returns a bound method created from an unbound method of the subclass. So calling it leads to an infinite recursion.There is the same issue with
persistent_loadmethod.PR #113579 uses a different approach, similar to the one used for
reducer_override. It removespersistent_idandpersistent_loaddescriptors and caches the bound methods, but only for the time of executiondump()andload()methods. It also adds the default implementations ofpersistent_idandpersistent_loadmethods in the C classes as well as in the Python classes.This approach may have disadvantage: calling
dump()andload()for small objects may now be slower due to added constant overhead. But I hope that the overhead is insignificant in comparison with other operations (IO, memory management).gh-89850: Add default C implementations of persistent_id() and persis…
pythongh-89850: Add default C implementations of persistent_id() and …
pythongh-89850: Add default C implementations of persistent_id() and …
pythongh-89850: Add default C implementations of persistent_id() and …