diff --git a/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst b/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst new file mode 100644 index 00000000000000..9641d11deb84d6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst @@ -0,0 +1,2 @@ +Avoid a crash when a :class:`threading.local` subclass finalizer accesses +attributes after construction fails. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 34c049ed81484a..d0847af17cc625 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1470,10 +1470,25 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw) assert(module != NULL); thread_module_state *state = get_thread_state(module); + // A subclass finalizer may access attributes if construction fails. + PyObject *localdicts = PyDict_New(); + if (localdicts == NULL) { + return NULL; + } + PyObject *thread_watchdogs = PySet_New(NULL); + if (thread_watchdogs == NULL) { + Py_DECREF(localdicts); + return NULL; + } + localobject *self = (localobject *)type->tp_alloc(type, 0); if (self == NULL) { + Py_DECREF(localdicts); + Py_DECREF(thread_watchdogs); return NULL; } + self->localdicts = localdicts; + self->thread_watchdogs = thread_watchdogs; // gh-128691: Use deferred reference counting for thread-locals to avoid // contention on the shared object. @@ -1482,16 +1497,6 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw) self->args = Py_XNewRef(args); self->kw = Py_XNewRef(kw); - self->localdicts = PyDict_New(); - if (self->localdicts == NULL) { - goto err; - } - - self->thread_watchdogs = PySet_New(NULL); - if (self->thread_watchdogs == NULL) { - goto err; - } - PyObject *localsdict = NULL; PyObject *sentinel_wr = NULL; if (create_localsdict(self, state, &localsdict, &sentinel_wr) < 0) {