Skip to content

Difference in ressurrection behavior with __del__ in py2 vs. py3 #76558

Closed

Description

@EricCousineau-TRI
BPO 32377
Nosy @pitrou, @EricCousineau-TRI
PRs
  • bpo-32377: improve __del__ docs and fix mention about resurrection #4927
  • [3.6] bpo-32377: improve __del__ docs and fix mention about resurrection (GH-4927) #4929
  • Files
  • revive_test.py: Repro example
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2017-12-19.19:00:56.948>
    created_at = <Date 2017-12-19.16:29:59.034>
    labels = ['type-bug', '3.7', 'docs']
    title = 'Difference in ressurrection behavior with __del__ in py2 vs. py3'
    updated_at = <Date 2020-04-09.15:25:33.816>
    user = 'https://github.com/EricCousineau-TRI'

    bugs.python.org fields:

    activity = <Date 2020-04-09.15:25:33.816>
    actor = 'Eric Cousineau'
    assignee = 'docs@python'
    closed = True
    closed_date = <Date 2017-12-19.19:00:56.948>
    closer = 'pitrou'
    components = ['Documentation']
    creation = <Date 2017-12-19.16:29:59.034>
    creator = 'Eric Cousineau'
    dependencies = []
    files = ['47339']
    hgrepos = []
    issue_num = 32377
    keywords = ['patch']
    message_count = 9.0
    messages = ['308660', '308661', '308662', '308664', '308665', '308667', '308671', '366057', '366068']
    nosy_count = 3.0
    nosy_names = ['pitrou', 'docs@python', 'Eric Cousineau']
    pr_nums = ['4927', '4929']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue32377'
    versions = ['Python 3.6', 'Python 3.7']

    Activity

    1. EricCousineau-TRI commented on Dec 19, 2017

      EricCousineau-TRImannequin
      MannequinAuthor

      Due to how PyObject_CallFinalizer is written in python3, __del__ will only ever be called once.

      In my use case, I am experimenting with a feature in pybind11 to prevent slicing with Python class instances that inherit from pybind11-C++ base classes, which involves detecting when an instance loses all reference in Python (Py_REFCNT(...) == 0) but still has reference in C++ (shared_ptr::count() > 0), and reviving the Python portion when this situation happens.

      In python2, I could do this without a hitch, as a resurrected object could have its __del__ method called multiple times (through tp_dealloc I believe?). But in python3, the object is marked with _PyGC_SET_FINALIZED(...), thus preventing __del__ from being called again.

      It'd be nice to either (a) somehow allow __del__ to be called naturally without too much fuss or, at the least, (b) have this reflected in the documentation:
      https://docs.python.org/3/reference/datamodel.html#object.__del__

      See attached revive_test. Example execution:

      $ python2 ./revive_test.py 
      Revive
      Destroy
      [ Done ]
      
      $ python3 ./revive_test.py 
      Revive
      [ Done ]
      
    2. added
      docsDocumentation in the Doc dir
      type-bugAn unexpected behavior, bug, or error
      on Dec 19, 2017
    3. pitrou commented on Dec 19, 2017

      @pitrou
      Member

      Thanks for the report. Apparently I forgot to update that piece of documentation when PEP-442 was implemented.

    4. EricCousineau-TRI commented on Dec 19, 2017

      EricCousineau-TRImannequin
      MannequinAuthor

      You're welcome, and thank you for the prompt response!

      I will say that it feels a tad odd to only have tp_finalize be called once for the entire lifetime of the object, while still having the option of it being resurrected.
      Is there any way to somehow "un-mark" the object to enable this workflow that I would like to have?

      My current hack is to call _PyGC_SET_FINALIZED(self, 0) - may I ask if there is a simpler way to do this?

    5. pitrou commented on Dec 19, 2017

      @pitrou
      Member

      Le 19/12/2017 à 17:46, Eric Cousineau a écrit :

      My current hack is to call _PyGC_SET_FINALIZED(self, 0) - may I ask if there is a simpler way to do this?

      Well... perhaps you could create another PyObject (it's just a wrapper,
      right?) since the old one doesn't have any outside references to it
      remaining.

      Note that calling __del__ only once is also how PyPy works:
      http://doc.pypy.org/en/latest/cpython_differences.html#differences-related-to-garbage-collection-strategies

      If there is some demand we could expose a higher-level spelling of
      _PyGC_SET_FINALIZED(self, 0).

    6. pitrou commented on Dec 19, 2017

      @pitrou
      Member

      I've proposed a documentation improvement in #4927 . Please chime in if you see have issues with it.

    7. pitrou commented on Dec 19, 2017

      @pitrou
      Member

      New changeset 4b96593 by Antoine Pitrou in branch 'master':
      bpo-32377: improve __del__ docs and fix mention about resurrection (bpo-4927)
      4b96593

    8. pitrou commented on Dec 19, 2017

      @pitrou
      Member

      New changeset dc5770b by Antoine Pitrou (Miss Islington (bot)) in branch '3.6':
      bpo-32377: improve __del__ docs and fix mention about resurrection (GH-4927) (bpo-4929)
      dc5770b

    9. EricCousineau-TRI commented on Apr 9, 2020

      EricCousineau-TRImannequin
      MannequinAuthor

      Super late response, but for this part:

      Well... perhaps you could create another PyObject (it's just a wrapper,
      right?) since the old one doesn't have any outside references to it
      remaining.

      In certain cases, yes, that would be the case. I do have unittests that
      (hackily) check id(o) before and after resurrection by using a weakref,
      so I could relax that contract.

      However, in other cases, the "wrapper" object in Python is actually a C++
      base class that has been derived from in Python. If ownership of the C++
      instance (which should more-or-less own the Python portion) goes solely to C++,
      then the Python portion could be garbage collected, and you get a sort-of
      slicing effect:
      pybind/pybind11#1145

      Granted, within this web of issues, one alternative is to always extend the
      lifetime of the Python object by the C++ portion. However, that creates a
      (opaque?) reference cycle between C++ and CPython, so I am a bit afraid of what
      that would do. I would prefer to make memory management conservative if
      possible.

      Or rather: It's something cool to try out, but I have not yet had time to dig
      in :(

    10. EricCousineau-TRI commented on Apr 9, 2020

      EricCousineau-TRImannequin
      MannequinAuthor

      See also bpo-40240: "Expose public spelling of _PyGC_FINALIZED and _PyGC_SET_FINALIZED?"

    11. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.7 (EOL)end of lifedocsDocumentation in the Doc dirtype-bugAn unexpected behavior, bug, or error

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions