classification
Title: threading.local doesn't free attrs when assigning thread exits
Type: behavior Stage:
Components: Extension Modules, Library (Lib) Versions: Python 3.0, Python 2.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: gregory.p.smith Nosy List: amaury.forgeotdarc, christian.heimes, gregory.p.smith, loewis, pfein, pitrou, tamino (7)
Priority: high Keywords: patch

Created on 2008-01-18 23:55 by pfein, last changed 2008-12-10 09:16 by loewis.

Files
File name Uploaded Description Edit Remove
_threading_local.py.patch pfein, 2008-01-18 23:55 patch for threading.local doctests
threading_local.patch amaury.forgeotdarc, 2008-01-19 17:43
test1868.py tamino, 2008-08-28 20:24 Test program
threading_local2.patch christian.heimes, 2008-08-28 21:09
threading_local3.patch tamino, 2008-08-28 22:11 Slightly modified version of threading_local2.patch
threading_local4.patch gregory.p.smith, 2008-09-02 06:42 based on threading_local3, includes the unit test and fixes one code review comment.
Messages (20)
msg60127 - (view) Author: Peter Fein (pfein) Date: 2008-01-18 23:55
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
msg60164 - (view) Author: Christian Heimes (christian.heimes) Date: 2008-01-19 14:05
I've added a test in r60075.

It shows that threading.local cleans up thread local data except for the
last stopped thread.
msg60180 - (view) Author: Peter Fein (pfein) Date: 2008-01-19 14:50
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).
msg60183 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) Date: 2008-01-19 15:20
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.
msg60203 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) Date: 2008-01-19 17:43
Here is a patch that removes the member localobject->dict.
Now the dictionary is always retrieved from the thread state.
msg72062 - (view) Author: Gregory P. Smith (gregory.p.smith) Date: 2008-08-28 04:44
see also #3710
msg72063 - (view) Author: Gregory P. Smith (gregory.p.smith) Date: 2008-08-28 05:29
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 #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
msg72110 - (view) Author: Ben Cottrell (tamino) Date: 2008-08-28 20:24
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.
msg72112 - (view) Author: Christian Heimes (christian.heimes) Date: 2008-08-28 21:09
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.
msg72115 - (view) Author: Ben Cottrell (tamino) Date: 2008-08-28 22:11
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.
msg72315 - (view) Author: Gregory P. Smith (gregory.p.smith) Date: 2008-09-02 06:42
this is ready for more review at http://codereview.appspot.com/3641
msg72333 - (view) Author: Antoine Pitrou (pitrou) Date: 2008-09-02 12:14
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 #3757.
msg72335 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) Date: 2008-09-02 12:55
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()?
msg72336 - (view) Author: Christian Heimes (christian.heimes) Date: 2008-09-02 13:21
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
msg72799 - (view) Author: Gregory P. Smith (gregory.p.smith) Date: 2008-09-08 22:24
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.
msg72974 - (view) Author: Martin v. Löwis (loewis) Date: 2008-09-10 16:17
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.
msg72985 - (view) Author: Gregory P. Smith (gregory.p.smith) Date: 2008-09-10 21:07
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.
msg76584 - (view) Author: Martin v. Löwis (loewis) Date: 2008-11-29 08:11
Is any of the patches believed to be ready?
msg76589 - (view) Author: Martin v. Löwis (loewis) Date: 2008-11-29 12:46
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.
msg77520 - (view) Author: Martin v. Löwis (loewis) Date: 2008-12-10 09:16
Since no specific patch was proposed for 2.5.3, I'm untargetting it for 2.5.
History
Date User Action Args
2008-12-10 09:16:55loewissetmessages: + msg77520
versions: - Python 2.5, Python 2.5.3
2008-11-29 12:46:59loewissetkeywords: - needs review
messages: + msg76589
2008-11-29 08:11:14loewissetmessages: + msg76584
2008-11-29 04:48:35gregory.p.smithsetversions: + Python 2.5.3
2008-09-10 21:07:45gregory.p.smithsetmessages: + msg72985
2008-09-10 16:17:30loewissetpriority: release blocker -> high
nosy: + loewis
messages: + msg72974
2008-09-09 13:14:43barrysetpriority: deferred blocker -> release blocker
2008-09-08 22:24:35gregory.p.smithsetmessages: + msg72799
2008-09-04 01:16:12benjamin.petersonsetpriority: release blocker -> deferred blocker
2008-09-02 13:21:03christian.heimessetmessages: + msg72336
2008-09-02 12:55:37amaury.forgeotdarcsetmessages: + msg72335
2008-09-02 12:14:42pitrousetnosy: + pitrou
messages: + msg72333
2008-09-02 06:42:52gregory.p.smithsetfiles: + threading_local4.patch
messages: + msg72315
2008-09-02 05:48:31gregory.p.smithsetassignee: gregory.p.smith
2008-08-28 22:11:33taminosetfiles: + threading_local3.patch
messages: + msg72115
2008-08-28 21:09:19christian.heimessetfiles: + threading_local2.patch
messages: + msg72112
2008-08-28 20:24:49taminosetfiles: + test1868.py
nosy: + tamino
messages: + msg72110
2008-08-28 05:29:26gregory.p.smithsetpriority: high -> release blocker
keywords: + needs review
messages: + msg72063
2008-08-28 04:44:36gregory.p.smithsetmessages: + msg72062
2008-08-28 04:37:36gregory.p.smithsetkeywords: + patch
nosy: + gregory.p.smith
2008-01-19 17:43:55amaury.forgeotdarcsetfiles: + threading_local.patch
messages: + msg60203
2008-01-19 15:20:53amaury.forgeotdarcsetnosy: + amaury.forgeotdarc
messages: + msg60183
2008-01-19 14:50:56pfeinsetmessages: + msg60180
2008-01-19 14:05:31christian.heimessetnosy: + christian.heimes
messages: + msg60164
2008-01-19 00:10:14christian.heimessetpriority: high
components: + Extension Modules
versions: + Python 2.6, Python 3.0
2008-01-18 23:55:06pfeincreate