mirror of https://github.com/python/cpython.git
Merged revisions 83918 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r83918 | antoine.pitrou | 2010-08-10 00:38:19 +0200 (mar., 10 août 2010) | 5 lines Issue #3757: thread-local objects now support cyclic garbage collection. Thread-local objects involved in reference cycles will be deallocated timely by the cyclic GC, even if the underlying thread is still running. ........
This commit is contained in:
parent
c751d446e7
commit
9900916df4
|
@ -1,10 +1,15 @@
|
||||||
import unittest
|
import unittest
|
||||||
from doctest import DocTestSuite
|
from doctest import DocTestSuite
|
||||||
from test import test_support
|
from test import test_support
|
||||||
threading = test_support.import_module('threading')
|
|
||||||
import weakref
|
import weakref
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
|
# Modules under test
|
||||||
|
_thread = test_support.import_module('thread')
|
||||||
|
threading = test_support.import_module('threading')
|
||||||
|
import _threading_local
|
||||||
|
|
||||||
|
|
||||||
class Weak(object):
|
class Weak(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -13,7 +18,7 @@ def target(local, weaklist):
|
||||||
local.weak = weak
|
local.weak = weak
|
||||||
weaklist.append(weakref.ref(weak))
|
weaklist.append(weakref.ref(weak))
|
||||||
|
|
||||||
class ThreadingLocalTest(unittest.TestCase):
|
class BaseLocalTest:
|
||||||
|
|
||||||
def test_local_refs(self):
|
def test_local_refs(self):
|
||||||
self._local_refs(20)
|
self._local_refs(20)
|
||||||
|
@ -21,7 +26,7 @@ def test_local_refs(self):
|
||||||
self._local_refs(100)
|
self._local_refs(100)
|
||||||
|
|
||||||
def _local_refs(self, n):
|
def _local_refs(self, n):
|
||||||
local = threading.local()
|
local = self._local()
|
||||||
weaklist = []
|
weaklist = []
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
t = threading.Thread(target=target, args=(local, weaklist))
|
t = threading.Thread(target=target, args=(local, weaklist))
|
||||||
|
@ -32,9 +37,9 @@ def _local_refs(self, n):
|
||||||
gc.collect()
|
gc.collect()
|
||||||
self.assertEqual(len(weaklist), n)
|
self.assertEqual(len(weaklist), n)
|
||||||
|
|
||||||
# XXX threading.local keeps the local of the last stopped thread alive.
|
# XXX _threading_local keeps the local of the last stopped thread alive.
|
||||||
deadlist = [weak for weak in weaklist if weak() is None]
|
deadlist = [weak for weak in weaklist if weak() is None]
|
||||||
self.assertEqual(len(deadlist), n-1)
|
self.assertIn(len(deadlist), (n-1, n))
|
||||||
|
|
||||||
# Assignment to the same thread local frees it sometimes (!)
|
# Assignment to the same thread local frees it sometimes (!)
|
||||||
local.someothervar = None
|
local.someothervar = None
|
||||||
|
@ -48,7 +53,7 @@ def test_derived(self):
|
||||||
# is created but not correctly set on the object.
|
# is created but not correctly set on the object.
|
||||||
# The first member set may be bogus.
|
# The first member set may be bogus.
|
||||||
import time
|
import time
|
||||||
class Local(threading.local):
|
class Local(self._local):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
local = Local()
|
local = Local()
|
||||||
|
@ -69,7 +74,7 @@ def f(i):
|
||||||
|
|
||||||
def test_derived_cycle_dealloc(self):
|
def test_derived_cycle_dealloc(self):
|
||||||
# http://bugs.python.org/issue6990
|
# http://bugs.python.org/issue6990
|
||||||
class Local(threading.local):
|
class Local(self._local):
|
||||||
pass
|
pass
|
||||||
locals = None
|
locals = None
|
||||||
passed = [False]
|
passed = [False]
|
||||||
|
@ -121,10 +126,32 @@ def __init__(self, *args, **kwargs):
|
||||||
self.assertRaises(TypeError, cls, 1)
|
self.assertRaises(TypeError, cls, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadLocalTest(unittest.TestCase, BaseLocalTest):
|
||||||
|
_local = _thread._local
|
||||||
|
|
||||||
|
# Fails for the pure Python implementation
|
||||||
|
def test_cycle_collection(self):
|
||||||
|
class X:
|
||||||
|
pass
|
||||||
|
|
||||||
|
x = X()
|
||||||
|
x.local = self._local()
|
||||||
|
x.local.x = x
|
||||||
|
wr = weakref.ref(x)
|
||||||
|
del x
|
||||||
|
gc.collect()
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
|
||||||
|
class PyThreadingLocalTest(unittest.TestCase, BaseLocalTest):
|
||||||
|
_local = _threading_local.local
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
suite.addTest(DocTestSuite('_threading_local'))
|
suite.addTest(DocTestSuite('_threading_local'))
|
||||||
suite.addTest(unittest.makeSuite(ThreadingLocalTest))
|
suite.addTest(unittest.makeSuite(ThreadLocalTest))
|
||||||
|
suite.addTest(unittest.makeSuite(PyThreadingLocalTest))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from thread import _local
|
from thread import _local
|
||||||
|
|
|
@ -29,6 +29,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #3757: thread-local objects now support cyclic garbage collection.
|
||||||
|
Thread-local objects involved in reference cycles will be deallocated
|
||||||
|
timely by the cyclic GC, even if the underlying thread is still running.
|
||||||
|
|
||||||
- Issue #6231: Fix xml.etree.ElementInclude to include the tail of the
|
- Issue #6231: Fix xml.etree.ElementInclude to include the tail of the
|
||||||
current node.
|
current node.
|
||||||
|
|
||||||
|
|
|
@ -177,19 +177,163 @@ newlockobject(void)
|
||||||
|
|
||||||
#include "structmember.h"
|
#include "structmember.h"
|
||||||
|
|
||||||
|
/* Quick overview:
|
||||||
|
|
||||||
|
We need to be able to reclaim reference cycles as soon as possible
|
||||||
|
(both when a thread is being terminated, or a thread-local object
|
||||||
|
becomes unreachable from user data). Constraints:
|
||||||
|
- it must not be possible for thread-state dicts to be involved in
|
||||||
|
reference cycles (otherwise the cyclic GC will refuse to consider
|
||||||
|
objects referenced from a reachable thread-state dict, even though
|
||||||
|
local_dealloc would clear them)
|
||||||
|
- the death of a thread-state dict must still imply destruction of the
|
||||||
|
corresponding local dicts in all thread-local objects.
|
||||||
|
|
||||||
|
Our implementation uses small "localdummy" objects in order to break
|
||||||
|
the reference chain. These trivial objects are hashable (using the
|
||||||
|
default scheme of identity hashing) and weakrefable.
|
||||||
|
Each thread-state holds a separate localdummy for each local object
|
||||||
|
(as a /strong reference/),
|
||||||
|
and each thread-local object holds a dict mapping /weak references/
|
||||||
|
of localdummies to local dicts.
|
||||||
|
|
||||||
|
Therefore:
|
||||||
|
- only the thread-state dict holds a strong reference to the dummies
|
||||||
|
- only the thread-local object holds a strong reference to the local dicts
|
||||||
|
- only outside objects (application- or library-level) hold strong
|
||||||
|
references to the thread-local objects
|
||||||
|
- as soon as a thread-state dict is destroyed, the weakref callbacks of all
|
||||||
|
dummies attached to that thread are called, and destroy the corresponding
|
||||||
|
local dicts from thread-local objects
|
||||||
|
- as soon as a thread-local object is destroyed, its local dicts are
|
||||||
|
destroyed and its dummies are manually removed from all thread states
|
||||||
|
- the GC can do its work correctly when a thread-local object is dangling,
|
||||||
|
without any interference from the thread-state dicts
|
||||||
|
|
||||||
|
As an additional optimization, each localdummy holds a borrowed reference
|
||||||
|
to the corresponding localdict. This borrowed reference is only used
|
||||||
|
by the thread-local object which has created the localdummy, which should
|
||||||
|
guarantee that the localdict still exists when accessed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *localdict; /* Borrowed reference! */
|
||||||
|
PyObject *weakreflist; /* List of weak references to self */
|
||||||
|
} localdummyobject;
|
||||||
|
|
||||||
|
static void
|
||||||
|
localdummy_dealloc(localdummyobject *self)
|
||||||
|
{
|
||||||
|
if (self->weakreflist != NULL)
|
||||||
|
PyObject_ClearWeakRefs((PyObject *) self);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyTypeObject localdummytype = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
/* tp_name */ "_thread._localdummy",
|
||||||
|
/* tp_basicsize */ sizeof(localdummyobject),
|
||||||
|
/* tp_itemsize */ 0,
|
||||||
|
/* tp_dealloc */ (destructor)localdummy_dealloc,
|
||||||
|
/* tp_print */ 0,
|
||||||
|
/* tp_getattr */ 0,
|
||||||
|
/* tp_setattr */ 0,
|
||||||
|
/* tp_reserved */ 0,
|
||||||
|
/* tp_repr */ 0,
|
||||||
|
/* tp_as_number */ 0,
|
||||||
|
/* tp_as_sequence */ 0,
|
||||||
|
/* tp_as_mapping */ 0,
|
||||||
|
/* tp_hash */ 0,
|
||||||
|
/* tp_call */ 0,
|
||||||
|
/* tp_str */ 0,
|
||||||
|
/* tp_getattro */ 0,
|
||||||
|
/* tp_setattro */ 0,
|
||||||
|
/* tp_as_buffer */ 0,
|
||||||
|
/* tp_flags */ Py_TPFLAGS_DEFAULT,
|
||||||
|
/* tp_doc */ "Thread-local dummy",
|
||||||
|
/* tp_traverse */ 0,
|
||||||
|
/* tp_clear */ 0,
|
||||||
|
/* tp_richcompare */ 0,
|
||||||
|
/* tp_weaklistoffset */ offsetof(localdummyobject, weakreflist)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
PyObject *key;
|
PyObject *key;
|
||||||
PyObject *args;
|
PyObject *args;
|
||||||
PyObject *kw;
|
PyObject *kw;
|
||||||
|
/* The current thread's local dict (necessary for tp_dictoffset) */
|
||||||
PyObject *dict;
|
PyObject *dict;
|
||||||
|
PyObject *weakreflist; /* List of weak references to self */
|
||||||
|
/* A {localdummy weakref -> localdict} dict */
|
||||||
|
PyObject *dummies;
|
||||||
|
/* The callback for weakrefs to localdummies */
|
||||||
|
PyObject *wr_callback;
|
||||||
} localobject;
|
} localobject;
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
static PyObject *_ldict(localobject *self);
|
||||||
|
static PyObject *_localdummy_destroyed(PyObject *meth_self, PyObject *dummyweakref);
|
||||||
|
|
||||||
|
/* Create and register the dummy for the current thread, as well as the
|
||||||
|
corresponding local dict */
|
||||||
|
static int
|
||||||
|
_local_create_dummy(localobject *self)
|
||||||
|
{
|
||||||
|
PyObject *tdict, *ldict = NULL, *wr = NULL;
|
||||||
|
localdummyobject *dummy = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
tdict = PyThreadState_GetDict();
|
||||||
|
if (tdict == NULL) {
|
||||||
|
PyErr_SetString(PyExc_SystemError,
|
||||||
|
"Couldn't get thread-state dictionary");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ldict = PyDict_New();
|
||||||
|
if (ldict == NULL)
|
||||||
|
goto err;
|
||||||
|
dummy = (localdummyobject *) localdummytype.tp_alloc(&localdummytype, 0);
|
||||||
|
if (dummy == NULL)
|
||||||
|
goto err;
|
||||||
|
dummy->localdict = ldict;
|
||||||
|
wr = PyWeakref_NewRef((PyObject *) dummy, self->wr_callback);
|
||||||
|
if (wr == NULL)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
/* As a side-effect, this will cache the weakref's hash before the
|
||||||
|
dummy gets deleted */
|
||||||
|
r = PyDict_SetItem(self->dummies, wr, ldict);
|
||||||
|
if (r < 0)
|
||||||
|
goto err;
|
||||||
|
Py_CLEAR(wr);
|
||||||
|
r = PyDict_SetItem(tdict, self->key, (PyObject *) dummy);
|
||||||
|
if (r < 0)
|
||||||
|
goto err;
|
||||||
|
Py_CLEAR(dummy);
|
||||||
|
|
||||||
|
Py_CLEAR(self->dict);
|
||||||
|
self->dict = ldict;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
Py_XDECREF(ldict);
|
||||||
|
Py_XDECREF(wr);
|
||||||
|
Py_XDECREF(dummy);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
local_new(PyTypeObject *type, PyObject *args, PyObject *kw)
|
local_new(PyTypeObject *type, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
localobject *self;
|
localobject *self;
|
||||||
PyObject *tdict;
|
PyObject *wr;
|
||||||
|
static PyMethodDef wr_callback_def = {
|
||||||
|
"_localdummy_destroyed", (PyCFunction) _localdummy_destroyed, METH_O
|
||||||
|
};
|
||||||
|
|
||||||
if (type->tp_init == PyBaseObject_Type.tp_init
|
if (type->tp_init == PyBaseObject_Type.tp_init
|
||||||
&& ((args && PyObject_IsTrue(args))
|
&& ((args && PyObject_IsTrue(args))
|
||||||
|
@ -207,23 +351,25 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw)
|
||||||
self->args = args;
|
self->args = args;
|
||||||
Py_XINCREF(kw);
|
Py_XINCREF(kw);
|
||||||
self->kw = kw;
|
self->kw = kw;
|
||||||
self->dict = NULL; /* making sure */
|
|
||||||
self->key = PyString_FromFormat("thread.local.%p", self);
|
self->key = PyString_FromFormat("thread.local.%p", self);
|
||||||
if (self->key == NULL)
|
if (self->key == NULL)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
self->dict = PyDict_New();
|
self->dummies = PyDict_New();
|
||||||
if (self->dict == NULL)
|
if (self->dummies == NULL)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
tdict = PyThreadState_GetDict();
|
/* We use a weak reference to self in the callback closure
|
||||||
if (tdict == NULL) {
|
in order to avoid spurious reference cycles */
|
||||||
PyErr_SetString(PyExc_SystemError,
|
wr = PyWeakref_NewRef((PyObject *) self, NULL);
|
||||||
"Couldn't get thread-state dictionary");
|
if (wr == NULL)
|
||||||
|
goto err;
|
||||||
|
self->wr_callback = PyCFunction_New(&wr_callback_def, wr);
|
||||||
|
Py_DECREF(wr);
|
||||||
|
if (self->wr_callback == NULL)
|
||||||
goto err;
|
goto err;
|
||||||
}
|
|
||||||
|
|
||||||
if (PyDict_SetItem(tdict, self->key, self->dict) < 0)
|
if (_local_create_dummy(self) < 0)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
return (PyObject *)self;
|
return (PyObject *)self;
|
||||||
|
@ -238,6 +384,7 @@ local_traverse(localobject *self, visitproc visit, void *arg)
|
||||||
{
|
{
|
||||||
Py_VISIT(self->args);
|
Py_VISIT(self->args);
|
||||||
Py_VISIT(self->kw);
|
Py_VISIT(self->kw);
|
||||||
|
Py_VISIT(self->dummies);
|
||||||
Py_VISIT(self->dict);
|
Py_VISIT(self->dict);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -245,16 +392,13 @@ local_traverse(localobject *self, visitproc visit, void *arg)
|
||||||
static int
|
static int
|
||||||
local_clear(localobject *self)
|
local_clear(localobject *self)
|
||||||
{
|
{
|
||||||
|
PyThreadState *tstate;
|
||||||
Py_CLEAR(self->args);
|
Py_CLEAR(self->args);
|
||||||
Py_CLEAR(self->kw);
|
Py_CLEAR(self->kw);
|
||||||
|
Py_CLEAR(self->dummies);
|
||||||
Py_CLEAR(self->dict);
|
Py_CLEAR(self->dict);
|
||||||
return 0;
|
Py_CLEAR(self->wr_callback);
|
||||||
}
|
/* Remove all strong references to dummies from the thread states */
|
||||||
|
|
||||||
static void
|
|
||||||
local_dealloc(localobject *self)
|
|
||||||
{
|
|
||||||
PyThreadState *tstate;
|
|
||||||
if (self->key
|
if (self->key
|
||||||
&& (tstate = PyThreadState_Get())
|
&& (tstate = PyThreadState_Get())
|
||||||
&& tstate->interp) {
|
&& tstate->interp) {
|
||||||
|
@ -265,16 +409,29 @@ local_dealloc(localobject *self)
|
||||||
PyDict_GetItem(tstate->dict, self->key))
|
PyDict_GetItem(tstate->dict, self->key))
|
||||||
PyDict_DelItem(tstate->dict, self->key);
|
PyDict_DelItem(tstate->dict, self->key);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
local_dealloc(localobject *self)
|
||||||
|
{
|
||||||
|
/* Weakrefs must be invalidated right now, otherwise they can be used
|
||||||
|
from code called below, which is very dangerous since Py_REFCNT(self) == 0 */
|
||||||
|
if (self->weakreflist != NULL)
|
||||||
|
PyObject_ClearWeakRefs((PyObject *) self);
|
||||||
|
|
||||||
|
PyObject_GC_UnTrack(self);
|
||||||
|
|
||||||
Py_XDECREF(self->key);
|
|
||||||
local_clear(self);
|
local_clear(self);
|
||||||
|
Py_XDECREF(self->key);
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns a borrowed reference to the local dict, creating it if necessary */
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_ldict(localobject *self)
|
_ldict(localobject *self)
|
||||||
{
|
{
|
||||||
PyObject *tdict, *ldict;
|
PyObject *tdict, *ldict, *dummy;
|
||||||
|
|
||||||
tdict = PyThreadState_GetDict();
|
tdict = PyThreadState_GetDict();
|
||||||
if (tdict == NULL) {
|
if (tdict == NULL) {
|
||||||
|
@ -283,22 +440,11 @@ _ldict(localobject *self)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ldict = PyDict_GetItem(tdict, self->key);
|
dummy = PyDict_GetItem(tdict, self->key);
|
||||||
if (ldict == NULL) {
|
if (dummy == NULL) {
|
||||||
ldict = PyDict_New(); /* we own ldict */
|
if (_local_create_dummy(self) < 0)
|
||||||
|
|
||||||
if (ldict == NULL)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
else {
|
ldict = self->dict;
|
||||||
int i = PyDict_SetItem(tdict, self->key, ldict);
|
|
||||||
Py_DECREF(ldict); /* now ldict is borrowed */
|
|
||||||
if (i < 0)
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_CLEAR(self->dict);
|
|
||||||
Py_INCREF(ldict);
|
|
||||||
self->dict = ldict; /* still borrowed */
|
|
||||||
|
|
||||||
if (Py_TYPE(self)->tp_init != PyBaseObject_Type.tp_init &&
|
if (Py_TYPE(self)->tp_init != PyBaseObject_Type.tp_init &&
|
||||||
Py_TYPE(self)->tp_init((PyObject*)self,
|
Py_TYPE(self)->tp_init((PyObject*)self,
|
||||||
|
@ -309,14 +455,17 @@ _ldict(localobject *self)
|
||||||
PyDict_DelItem(tdict, self->key);
|
PyDict_DelItem(tdict, self->key);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(Py_TYPE(dummy) == &localdummytype);
|
||||||
|
ldict = ((localdummyobject *) dummy)->localdict;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The call to tp_init above may have caused another thread to run.
|
/* The call to tp_init above may have caused another thread to run.
|
||||||
Install our ldict again. */
|
Install our ldict again. */
|
||||||
if (self->dict != ldict) {
|
if (self->dict != ldict) {
|
||||||
Py_CLEAR(self->dict);
|
|
||||||
Py_INCREF(ldict);
|
Py_INCREF(ldict);
|
||||||
|
Py_CLEAR(self->dict);
|
||||||
self->dict = ldict;
|
self->dict = ldict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,13 +487,10 @@ local_setattro(localobject *self, PyObject *name, PyObject *v)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
local_getdict(localobject *self, void *closure)
|
local_getdict(localobject *self, void *closure)
|
||||||
{
|
{
|
||||||
if (self->dict == NULL) {
|
PyObject *ldict;
|
||||||
PyErr_SetString(PyExc_AttributeError, "__dict__");
|
ldict = _ldict(self);
|
||||||
return NULL;
|
Py_XINCREF(ldict);
|
||||||
}
|
return ldict;
|
||||||
|
|
||||||
Py_INCREF(self->dict);
|
|
||||||
return self->dict;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyGetSetDef local_getset[] = {
|
static PyGetSetDef local_getset[] = {
|
||||||
|
@ -375,12 +521,13 @@ static PyTypeObject localtype = {
|
||||||
/* tp_getattro */ (getattrofunc)local_getattro,
|
/* tp_getattro */ (getattrofunc)local_getattro,
|
||||||
/* tp_setattro */ (setattrofunc)local_setattro,
|
/* tp_setattro */ (setattrofunc)local_setattro,
|
||||||
/* tp_as_buffer */ 0,
|
/* tp_as_buffer */ 0,
|
||||||
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
|
| Py_TPFLAGS_HAVE_GC,
|
||||||
/* tp_doc */ "Thread-local data",
|
/* tp_doc */ "Thread-local data",
|
||||||
/* tp_traverse */ (traverseproc)local_traverse,
|
/* tp_traverse */ (traverseproc)local_traverse,
|
||||||
/* tp_clear */ (inquiry)local_clear,
|
/* tp_clear */ (inquiry)local_clear,
|
||||||
/* tp_richcompare */ 0,
|
/* tp_richcompare */ 0,
|
||||||
/* tp_weaklistoffset */ 0,
|
/* tp_weaklistoffset */ offsetof(localobject, weakreflist),
|
||||||
/* tp_iter */ 0,
|
/* tp_iter */ 0,
|
||||||
/* tp_iternext */ 0,
|
/* tp_iternext */ 0,
|
||||||
/* tp_methods */ 0,
|
/* tp_methods */ 0,
|
||||||
|
@ -421,6 +568,36 @@ local_getattro(localobject *self, PyObject *name)
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Called when a dummy is destroyed. */
|
||||||
|
static PyObject *
|
||||||
|
_localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref)
|
||||||
|
{
|
||||||
|
PyObject *obj;
|
||||||
|
localobject *self;
|
||||||
|
assert(PyWeakref_CheckRef(localweakref));
|
||||||
|
obj = PyWeakref_GET_OBJECT(localweakref);
|
||||||
|
if (obj == Py_None)
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
Py_INCREF(obj);
|
||||||
|
assert(PyObject_TypeCheck(obj, &localtype));
|
||||||
|
/* If the thread-local object is still alive and not being cleared,
|
||||||
|
remove the corresponding local dict */
|
||||||
|
self = (localobject *) obj;
|
||||||
|
if (self->dummies != NULL) {
|
||||||
|
PyObject *ldict;
|
||||||
|
ldict = PyDict_GetItem(self->dummies, dummyweakref);
|
||||||
|
if (ldict != NULL) {
|
||||||
|
if (ldict == self->dict)
|
||||||
|
Py_CLEAR(self->dict);
|
||||||
|
PyDict_DelItem(self->dummies, dummyweakref);
|
||||||
|
}
|
||||||
|
if (PyErr_Occurred())
|
||||||
|
PyErr_WriteUnraisable(obj);
|
||||||
|
}
|
||||||
|
Py_DECREF(obj);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Module functions */
|
/* Module functions */
|
||||||
|
|
||||||
struct bootstate {
|
struct bootstate {
|
||||||
|
@ -728,6 +905,8 @@ initthread(void)
|
||||||
PyObject *m, *d;
|
PyObject *m, *d;
|
||||||
|
|
||||||
/* Initialize types: */
|
/* Initialize types: */
|
||||||
|
if (PyType_Ready(&localdummytype) < 0)
|
||||||
|
return;
|
||||||
if (PyType_Ready(&localtype) < 0)
|
if (PyType_Ready(&localtype) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue