https://github.com/python/cpython/commit/a99e9298b9c38d9edf21657b0b66689854b1cbaa
commit: a99e9298b9c38d9edf21657b0b66689854b1cbaa
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-22T15:22:13-07:00
summary:

[3.14] gh-159739: Preserve ContextVar cache updates from finalizers (GH-160197) 
(#160329)



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.
(cherry picked from commit f4f8b933e3cf73c1ec8ca1d33ff62faee9f0ffd8)

Co-authored-by: Jelle Zijlstra <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.ghGctt.rst
M Lib/test/test_context.py
M Python/context.c

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 65b5d5a036eb36..13453a3ceab844 100644
--- a/Python/context.c
+++ b/Python/context.c
@@ -866,13 +866,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;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to