From 125f64c71e60e4aff04c2855c8928f76602f6ac1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 21 Oct 2026 21:29:33 -0700 Subject: [PATCH] gh-159739: Preserve ContextVar cache updates from finalizers Resetting a ContextVar can finalize the replaced value, whose finalizer may set the variable again. Updating the lookup cache after releasing the old context root overwrites that newer cached value, so get() can return a stale value or use a freed object. Update the cache before the old root can be released. Backport to 3.15 and 3.14. --- Lib/test/test_context.py | 13 +++++++++++++ .../2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst | 1 + Python/context.c | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py index f193a333521059..9df3e126231041 100644 --- a/Lib/test/test_context.py +++ b/Lib/test/test_context.py @@ -336,6 +336,19 @@ def fun(): contextvars.copy_context().run(fun) self.assertEqual(c.get(), []) + @isolated_context + def test_reset_with_reentrant_finalizer(self): + var = contextvars.ContextVar('var') + var.set('original') + + class Reenter: + def __del__(self): + var.set('finalizer') + + token = var.set(Reenter()) + var.reset(token) + self.assertEqual(var.get(), 'finalizer') + def test_context_copy_1(self): ctx1 = contextvars.Context() c = contextvars.ContextVar('c', default=42) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst new file mode 100644 index 00000000000000..dd9dd5dce88f43 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst @@ -0,0 +1 @@ +Fix :class:`contextvars.ContextVar` returning a stale value after a reentrant finalizer changes it during :meth:`~contextvars.ContextVar.reset`. diff --git a/Python/context.c b/Python/context.c index d48543c9e023a6..cc7b1179255d30 100644 --- a/Python/context.c +++ b/Python/context.c @@ -872,13 +872,13 @@ contextvar_set(PyContextVar *var, PyObject *val) return -1; } - context_set_vars(ctx, new_vars); - #ifndef Py_GIL_DISABLED var->var_cached = val; /* borrow */ var->var_cached_tsid = ts->id; var->var_cached_tsver = ts->context_ver; #endif + /* Releasing the old variables may run a finalizer that updates the cache. */ + context_set_vars(ctx, new_vars); return 0; }