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

threading.local doesn't free attrs when assigning thread exits #46176

Closed
pfein mannequin opened this issue Jan 18, 2008 · 24 comments
Closed

threading.local doesn't free attrs when assigning thread exits #46176

pfein mannequin opened this issue Jan 18, 2008 · 24 comments
Assignees
Labels
extension-modules C modules in the Modules dir stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@pfein
Copy link
Mannequin

pfein mannequin commented Jan 18, 2008

BPO 1868
Nosy @loewis, @gpshead, @amauryfa, @ncoghlan, @pitrou, @tiran
Files
  • _threading_local.py.patch: patch for threading.local doctests
  • threading_local.patch
  • test1868.py: Test program
  • threading_local2.patch
  • threading_local3.patch: Slightly modified version of threading_local2.patch
  • threading_local4.patch: based on threading_local3, includes the unit test and fixes one code review comment.
  • threadlocal.patch
  • 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 = 'https://github.com/pitrou'
    closed_at = <Date 2010-08-28.18:30:12.472>
    created_at = <Date 2008-01-18.23:55:06.051>
    labels = ['extension-modules', 'type-bug', 'library']
    title = "threading.local doesn't free attrs when assigning thread exits"
    updated_at = <Date 2010-08-30.22:01:57.562>
    user = 'https://bugs.python.org/pfein'

    bugs.python.org fields:

    activity = <Date 2010-08-30.22:01:57.562>
    actor = 'ncoghlan'
    assignee = 'pitrou'
    closed = True
    closed_date = <Date 2010-08-28.18:30:12.472>
    closer = 'pitrou'
    components = ['Extension Modules', 'Library (Lib)']
    creation = <Date 2008-01-18.23:55:06.051>
    creator = 'pfein'
    dependencies = []
    files = ['9212', '9225', '11296', '11297', '11298', '11341', '18603']
    hgrepos = []
    issue_num = 1868
    keywords = ['patch']
    message_count = 24.0
    messages = ['60127', '60164', '60180', '60183', '60203', '72062', '72063', '72110', '72112', '72115', '72315', '72333', '72335', '72336', '72799', '72974', '72985', '76584', '76589', '77520', '114574', '114577', '114689', '115165']
    nosy_count = 8.0
    nosy_names = ['loewis', 'gregory.p.smith', 'amaury.forgeotdarc', 'ncoghlan', 'pitrou', 'christian.heimes', 'pfein', 'tamino']
    pr_nums = []
    priority = 'high'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue1868'
    versions = ['Python 3.1', 'Python 2.7', 'Python 3.2']

    @pfein
    Copy link
    Mannequin Author

    pfein mannequin commented Jan 18, 2008

    threading.local doesn't free attributes assigned to a local() instance
    when the assigning thread exits. See attached patch for
    _threading_local.py doctests.

    Per discussion with Crys and arkanes in #python, this may be an issue
    with PyThreadState_Clear / PyThreadState_GetDict

    This appears to affect both thread._local and _threading_local.py

    @pfein pfein mannequin added stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Jan 18, 2008
    @tiran tiran added the extension-modules C modules in the Modules dir label Jan 19, 2008
    @tiran
    Copy link
    Member

    tiran commented Jan 19, 2008

    I've added a test in r60075.

    It shows that threading.local cleans up thread local data except for the
    last stopped thread.

    @pfein
    Copy link
    Mannequin Author

    pfein mannequin commented Jan 19, 2008

    re: r60075 : I'm unclear - is this intentional behavior?

    Adding a del t at line 24 after the loop doesn't help either (though
    it does make the test somewhat clear IMO).

    @amauryfa
    Copy link
    Member

    On the opposite, simply evaluating
    local.__dict__
    just before "deadlist = ...", frees the last value.

    Weird? I found that in threadmodule.c, the local object has a
    "self->dict" attribute, which contains the last used dictionary. The
    dictionaries switch when tp_getattro or tp_setattro are called from a
    different thread.

    Maybe localobject could be rewritten with no self->dict at all. Or make
    self->dict a *borrowed* reference inside the array of thread-local
    dictionaries.

    @amauryfa
    Copy link
    Member

    Here is a patch that removes the member localobject->dict.
    Now the dictionary is always retrieved from the thread state.

    @gpshead
    Copy link
    Member

    gpshead commented Aug 28, 2008

    see also bpo-3710

    @gpshead
    Copy link
    Member

    gpshead commented Aug 28, 2008

    I like Amaury's patch, it gets rid of what seems like an existing gross
    hack of having localobject->dict exist at all.

    I believe it may also fix bpo-3710 by getting rid of the unnecessary
    localobject->dict member.

    Could someone else please review this?

    threading_local.patch is available for review here:
    http://codereview.appspot.com/3641

    @tamino
    Copy link
    Mannequin

    tamino mannequin commented Aug 28, 2008

    I like this patch, too! I think it's a much cleaner way of implementing
    the thread._local type. However, when I test it, I have problems with
    subclasses of thread._local; using the class itself seems to work.

    I've attached a test program that shows the issue.

    @tiran
    Copy link
    Member

    tiran commented Aug 28, 2008

    Good catch, Ben!

    The generic setattr/getattr functions don't work as expected when the
    base class doesn't provide a __dict__. They are setting the attributes
    in the subclass' __dict__ instead of the thread local dict.

    My patch fixes the behavior. However it might be a better idea to make
    the threadlocal class non subclass-able.

    @tamino
    Copy link
    Mannequin

    tamino mannequin commented Aug 28, 2008

    Christian,

    Your patch works for me -- thanks!!

    I made a slight modification to your patch to allow "del" to work,
    and have attached my modified version.

    I agree that allowing subclassing makes thread._local harder to get
    right than it would otherwise be. There is code out there that uses
    that feature, though -- I'm running into it in the context of django,
    which (when using the sqlite database back end) keeps its sqlite
    connections in a subclass of thread._local.

    @gpshead gpshead self-assigned this Sep 2, 2008
    @gpshead
    Copy link
    Member

    gpshead commented Sep 2, 2008

    this is ready for more review at http://codereview.appspot.com/3641

    @pitrou
    Copy link
    Member

    pitrou commented Sep 2, 2008

    The patch is basically fine with me. While reviewing it I've found out
    that threading.local doesn't have cyclic garbage collecting enabled.
    I've opened a new issue for it in bpo-3757.

    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 2, 2008

    With the patch, subclasses of threading.local seem to have an empty
    __dict__:

    import threading
    class MyLocal(threading.local):
        pass
    
    l = MyLocal()
    l.x = 2
    l.__dict__   # returns {}

    Maybe __dict__ should be special-cased in local_getattro()?

    @tiran
    Copy link
    Member

    tiran commented Sep 2, 2008

    Amaury Forgeot d'Arc wrote:

    Maybe __dict__ should be special-cased in local_getattro()?

    With the patch it's also no longer possible to get a list of attribute
    names. +1 for special casing __dict__ in getattro and setattro.
    setattr(local, "__dict__", egg) should raise an AttributeError.

    Christian

    @gpshead
    Copy link
    Member

    gpshead commented Sep 8, 2008

    i won't have time to work on this for several days but i will happily
    review updated patches if anyone could contribute fixes for the __dict__
    issues described in the most recent comments.

    feel free to steal the issue from me.

    @loewis
    Copy link
    Mannequin

    loewis mannequin commented Sep 10, 2008

    If Christian's analysis is right, and memory is released except for the
    last thread, I don't think this qualifies as a release blocker.

    Also (unless I misinterpret the issue), it seems that we have made many
    releases already which had this bug, so it's unclear why it should block
    the next release.

    Tentatively lowering the priority to high.

    @loewis loewis mannequin removed the release-blocker label Sep 10, 2008
    @gpshead
    Copy link
    Member

    gpshead commented Sep 10, 2008

    Agreed, this is fine for a bugfix point release.

    I was initially concerned about the Modules/threadmodule.c struct
    localobject changing but that is private and used only by code within
    threadmodule.c so its not part of an API anything else could depend on.
    Changing it for 2.6.1/2.5.3/3.0.1 should be fine.

    @loewis
    Copy link
    Mannequin

    loewis mannequin commented Nov 29, 2008

    Is any of the patches believed to be ready?

    @loewis
    Copy link
    Mannequin

    loewis mannequin commented Nov 29, 2008

    Reconsidering: I think the "needs review" keyword should only be applied
    if there is a single patch associated with the issue that needs review.
    So anybody setting the keyword should remove all patches that don't need
    review anymore (as they are superceded). I'm removing the keyword for now.

    @loewis
    Copy link
    Mannequin

    loewis mannequin commented Dec 10, 2008

    Since no specific patch was proposed for 2.5.3, I'm untargetting it for 2.5.

    @pitrou
    Copy link
    Member

    pitrou commented Aug 21, 2010

    The original patch won't apply anymore, because of changes in the thread._local implementation.

    Instead, here is a new patch, which also adds tests for the __dict__ behaviour, and two new private API functions: _PyObject_Generic{Get,Set}AttrWithDict. These are the same as PyObject_Generic{Get,Set}Attr, except that it allows to pass the instance dict instead of letting the function discover it.

    @pitrou
    Copy link
    Member

    pitrou commented Aug 21, 2010

    It should be noted that the original reason for this issue is already gone in SVN (as part of the reimplementation alluded to above), but the timing fragility is still there since there can be a thread switch between the moment the "dict" member is set and the moment it is actually used. Hence the patch I've posted, to get rid of the "dict" member and instead handle lookups of the __dict__ attributes ourselves.

    @gpshead
    Copy link
    Member

    gpshead commented Aug 22, 2010

    your updated patch looks good to me. i've posted it here for easy review if anyone else wants to take a look:

    http://codereview.appspot.com/1995049/

    @gpshead gpshead assigned pitrou and unassigned gpshead Aug 22, 2010
    @pitrou
    Copy link
    Member

    pitrou commented Aug 28, 2010

    Thank you Gregory. I've committed the patch in r84344 (py3k), r84345 (3.1) and r84346 (2.7).

    @pitrou pitrou closed this as completed Aug 28, 2010
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    extension-modules C modules in the Modules dir stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    6 participants