Index: Lib/test/test_threading.py =================================================================== --- Lib/test/test_threading.py (revision 66142) +++ Lib/test/test_threading.py (working copy) @@ -438,10 +438,61 @@ self.assertRaises(RuntimeError, setattr, thread, "daemon", True) +# This test came from Ben Cottrell in http://bugs.python.org/issue1868 +class TestThreadingLocal(unittest.TestCase): + def setUp(self): + self._failed = "No error message set or cleared." + + def _test_one_class(self, c): + obj = c() + e1 = threading.Event() + e2 = threading.Event() + + def f1(): + obj.x = 'foo' + obj.y = 'bar' + del obj.y + e1.set() + e2.wait() + + def f2(): + try: + foo = obj.x + except AttributeError: + # This is expected -- we haven't set obj.x in this thread yet! + self._failed = "" # passed + else: + self._failed = ('Incorrectly got value %r from class %r\n' % + (foo, c)) + sys.stderr.write(self._failed) + + t1 = threading.Thread(target=f1) + t1.start() + e1.wait() + t2 = threading.Thread(target=f2) + t2.start() + t2.join() + + # The test is done; just let t1 know it can exit, and wait for it. + e2.set() + t1.join() + + def test_threading_local(self): + self._test_one_class(threading.local) + self.assert_(not self._failed, self._failed) + + def test_threading_local_subclass(self): + class LocalSubclass(threading.local): + """To test that subclasses behave properly.""" + self._test_one_class(LocalSubclass) + self.assert_(not self._failed, self._failed) + + def test_main(): test.test_support.run_unittest(ThreadTests, ThreadJoinOnShutdown, ThreadingExceptionTests, + TestThreadingLocal, ) if __name__ == "__main__": Index: Modules/threadmodule.c =================================================================== --- Modules/threadmodule.c (revision 66142) +++ Modules/threadmodule.c (working copy) @@ -164,7 +164,6 @@ PyObject *key; PyObject *args; PyObject *kw; - PyObject *dict; } localobject; static PyObject * @@ -172,6 +171,7 @@ { localobject *self; PyObject *tdict; + PyObject *localdict; if (type->tp_init == PyBaseObject_Type.tp_init && ((args && PyObject_IsTrue(args)) @@ -189,13 +189,12 @@ self->args = args; Py_XINCREF(kw); self->kw = kw; - self->dict = NULL; /* making sure */ self->key = PyString_FromFormat("thread.local.%p", self); if (self->key == NULL) goto err; - self->dict = PyDict_New(); - if (self->dict == NULL) + localdict = PyDict_New(); + if (localdict == NULL) goto err; tdict = PyThreadState_GetDict(); @@ -205,8 +204,11 @@ goto err; } - if (PyDict_SetItem(tdict, self->key, self->dict) < 0) + if (PyDict_SetItem(tdict, self->key, localdict) < 0) { + Py_DECREF(localdict); goto err; + } + Py_DECREF(localdict); return (PyObject *)self; @@ -220,7 +222,6 @@ { Py_VISIT(self->args); Py_VISIT(self->kw); - Py_VISIT(self->dict); return 0; } @@ -230,7 +231,6 @@ Py_CLEAR(self->key); Py_CLEAR(self->args); Py_CLEAR(self->kw); - Py_CLEAR(self->dict); return 0; } @@ -253,6 +253,7 @@ Py_TYPE(self)->tp_free((PyObject*)self); } +/* Return borrowed reference to the local dict */ static PyObject * _ldict(localobject *self) { @@ -278,10 +279,6 @@ return NULL; } - Py_CLEAR(self->dict); - Py_INCREF(ldict); - self->dict = ldict; /* still borrowed */ - if (Py_TYPE(self)->tp_init != PyBaseObject_Type.tp_init && Py_TYPE(self)->tp_init((PyObject*)self, self->args, self->kw) < 0) { @@ -294,39 +291,39 @@ } - /* The call to tp_init above may have caused another thread to run. - Install our ldict again. */ - if (self->dict != ldict) { - Py_CLEAR(self->dict); - Py_INCREF(ldict); - self->dict = ldict; - } - return ldict; } static int local_setattro(localobject *self, PyObject *name, PyObject *v) { - PyObject *ldict; - - ldict = _ldict(self); + PyObject *ldict = _ldict(self); if (ldict == NULL) return -1; - return PyObject_GenericSetAttr((PyObject *)self, name, v); + if (v != NULL) { + return PyDict_SetItem(ldict, name, v); + } else { + int res; + res = PyDict_DelItem(ldict, name); + if ((res < 0) && PyErr_ExceptionMatches(PyExc_KeyError)) { + PyErr_SetObject(PyExc_AttributeError, name); + } + return res; + } } static PyObject * local_getdict(localobject *self, void *closure) { - if (self->dict == NULL) { + PyObject *ldict = _ldict(self); + if (ldict == NULL) { PyErr_SetString(PyExc_AttributeError, "__dict__"); return NULL; } - Py_INCREF(self->dict); - return self->dict; + Py_INCREF(ldict); + return ldict; } static PyGetSetDef local_getset[] = { @@ -372,7 +369,7 @@ /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ 0, /* tp_descr_set */ 0, - /* tp_dictoffset */ offsetof(localobject, dict), + /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ local_new, @@ -389,10 +386,6 @@ if (ldict == NULL) return NULL; - if (Py_TYPE(self) != &localtype) - /* use generic lookup for subtypes */ - return PyObject_GenericGetAttr((PyObject *)self, name); - /* Optimization: just look in dict ourselves */ value = PyDict_GetItem(ldict, name); if (value == NULL)