Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-28416: Break reference cycles in Pickler and Unpickler subclasses #4080

Merged

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Oct 23, 2017

with the persistent_id() and persistent_load() methods.

https://bugs.python.org/issue28416

with the persistent_id() and persistent_load() methods.
@serhiy-storchaka
Copy link
Member Author

@vstinner, @pitrou, could you please look at this?

@support.cpython_only
def test_pickler_reference_cycle(self):
def check(Pickler):
for bin in 0, 1:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what is bin? Do you mean protocol?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A flag text/binary. Later it became a protocol, but the attribute bin still is used in sources (it is equal protocol != 0). Here it is enough to test only one binary protocol.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... It's still a bit cryptic though. And it doesn't cost anything to loop over all protocols, right? :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

func = _PyObject_GetAttrId(self, name);
if (func == NULL) {
*unbound = 0;
Py_CLEAR(*method);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two lines above seem redundant with the function beginning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually lines at function beginning are redundant.

if (f != NULL) {
return f(func, self, (PyObject *)Py_TYPE(self));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this fall through? May we return a non-bound method when unbound is true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting question.

@@ -360,6 +360,81 @@ _Pickle_FastCall(PyObject *func, PyObject *obj)

/*************************************************************************/

static int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the role of those three functions would be clearer if they took a {PyObject *func, int unbound} structure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. This function returns a pair: a pointer to bound or unbound method, and a flag for distinguishing them. There are other functions that return the same pair: _PyObject_GetMethod() and lookup_maybe_method() in typeobject.c. But the interface is different. _PyObject_GetMethod() returns a flag and sets a pointer by reference, lookup_maybe_method() returns a pointer and sets a flag by reference, this functions sets both pointer and flag by reference (and it also decrement the refcount of the old pointer). I tried different variants, but I don't know what interface is better. Well, will try to use a structure.

@serhiy-storchaka
Copy link
Member Author

Using a structure will lead to larger diff. Currently the code that just examine pers_func and handle it as a regular object reference (deallocators, etc) is not changed.

But I tried other refactoring. Now an additional parameter is not a flag, but a borrowed reference to self or NULL. This simplifies some calls.

}
}

/* Bound a method if it was deconstructed */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infinitive is "bind", not "bound".

@pitrou
Copy link
Member

pitrou commented Nov 29, 2017

Thank you. This looks good to me now.

@serhiy-storchaka serhiy-storchaka merged commit 986375e into python:master Nov 30, 2017
@miss-islington
Copy link
Contributor

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@serhiy-storchaka serhiy-storchaka deleted the pickle-reference-cycle branch November 30, 2017 20:48
@bedevere-bot
Copy link

GH-4653 is a backport of this pull request to the 3.6 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Nov 30, 2017
…pythonGH-4080)

with the persistent_id() and persistent_load() methods.
(cherry picked from commit 986375e)
@serhiy-storchaka
Copy link
Member Author

Thanks Antoine!

serhiy-storchaka pushed a commit that referenced this pull request Nov 30, 2017
…GH-4080) (#4653)

with the persistent_id() and persistent_load() methods.
(cherry picked from commit 986375e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants