From 5493663623c031460f10066d558e9afbcbf0b638 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Thu, 23 Apr 2026 08:36:47 +0200 Subject: [PATCH 1/5] fix: prevent crash in hamt --- Python/context.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Python/context.c b/Python/context.c index d48543c9e023a6..7d4b35be0ac0f0 100644 --- a/Python/context.c +++ b/Python/context.c @@ -866,8 +866,16 @@ contextvar_set(PyContextVar *var, PyObject *val) return -1; } + // gh-148891: _PyHamt_Assoc can allocate HAMT nodes, which may trigger + // GC. A weakref callback or finalizer could then re-enter + // contextvar_set, replacing ctx->ctx_vars via Py_SETREF and freeing + // the old HAMT while _PyHamt_Assoc is still traversing it. + // Prevent this by holding a strong reference to the current vars. + PyHamtObject *old_vars = context_get_current_vars(ctx); + Py_INCREF(old_vars); PyHamtObject *new_vars = _PyHamt_Assoc( - context_get_current_vars(ctx), (PyObject *)var, val); + old_vars, (PyObject *)var, val); + Py_DECREF(old_vars); if (new_vars == NULL) { return -1; } @@ -894,8 +902,12 @@ contextvar_del(PyContextVar *var) return -1; } + // gh-148891: same borrowed-reference hazard as contextvar_set: + // _PyHamt_Without allocates nodes, which may trigger GC re-entrancy. PyHamtObject *vars = context_get_current_vars(ctx); + Py_INCREF(vars); PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var); + Py_DECREF(vars); if (new_vars == NULL) { return -1; } From 1971be817bfae5ad72c1f263de0dbe573e381b2a Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 27 Apr 2026 14:53:16 +0200 Subject: [PATCH 2/5] misc: add news entry --- .../2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst new file mode 100644 index 00000000000000..cafc6bdb7afb44 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst @@ -0,0 +1,6 @@ +Fix a crash in :func:`contextvars.Context.run` where a re-entrant GC +invocation (triggered by ``_PyHamt_Assoc`` or ``_PyHamt_Without`` +allocating nodes) could free the current HAMT while it was still being +traversed. The fix holds a strong reference to the old ``ctx_vars`` +across the hazardous call in ``contextvar_set`` and ``contextvar_del``. + From 68e43d0a8a246c55d5b436727410acacc93b47fc Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Wed, 21 Oct 2026 18:45:38 +0200 Subject: [PATCH 3/5] review: update release note --- .../2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst index cafc6bdb7afb44..bae5c1366ca6a2 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-23-08-36-47.gh-issue-148891.AMMHow.rst @@ -1,6 +1 @@ -Fix a crash in :func:`contextvars.Context.run` where a re-entrant GC -invocation (triggered by ``_PyHamt_Assoc`` or ``_PyHamt_Without`` -allocating nodes) could free the current HAMT while it was still being -traversed. The fix holds a strong reference to the old ``ctx_vars`` -across the hazardous call in ``contextvar_set`` and ``contextvar_del``. - +Fix a crash in :func:`contextvars.Context.run` where a GC invocation could free the current HAMT during traverse. From d6ac3bf64e2ac3873839e38408e1094091a10d28 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Wed, 21 Oct 2026 19:20:33 +0200 Subject: [PATCH 4/5] chore: shorten comment --- Python/context.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Python/context.c b/Python/context.c index 7d4b35be0ac0f0..fda0721b85a5f3 100644 --- a/Python/context.c +++ b/Python/context.c @@ -866,11 +866,8 @@ contextvar_set(PyContextVar *var, PyObject *val) return -1; } - // gh-148891: _PyHamt_Assoc can allocate HAMT nodes, which may trigger - // GC. A weakref callback or finalizer could then re-enter - // contextvar_set, replacing ctx->ctx_vars via Py_SETREF and freeing - // the old HAMT while _PyHamt_Assoc is still traversing it. - // Prevent this by holding a strong reference to the current vars. + // gh-148891: _PyHamt_Assoc may allocate nodes, which may trigger GC. + // Bump the reference count to prevent the HAMT from being freed during traversal. PyHamtObject *old_vars = context_get_current_vars(ctx); Py_INCREF(old_vars); PyHamtObject *new_vars = _PyHamt_Assoc( @@ -902,8 +899,8 @@ contextvar_del(PyContextVar *var) return -1; } - // gh-148891: same borrowed-reference hazard as contextvar_set: - // _PyHamt_Without allocates nodes, which may trigger GC re-entrancy. + // gh-148891: _PyHamt_Without may allocate nodes, which may trigger GC. + // Bump the reference count to prevent the HAMT from being freed during traversal. PyHamtObject *vars = context_get_current_vars(ctx); Py_INCREF(vars); PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var); From 20725f4b32de7dcbcb2bb8bf4356afd714f6d7e1 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Wed, 21 Oct 2026 19:29:59 +0200 Subject: [PATCH 5/5] chore: shorten comment further --- Python/context.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Python/context.c b/Python/context.c index fda0721b85a5f3..65b3f25e700beb 100644 --- a/Python/context.c +++ b/Python/context.c @@ -866,8 +866,8 @@ contextvar_set(PyContextVar *var, PyObject *val) return -1; } - // gh-148891: _PyHamt_Assoc may allocate nodes, which may trigger GC. - // Bump the reference count to prevent the HAMT from being freed during traversal. + // gh-148891: Make a strong reference so a GC during _PyHamt_Assoc will + // not free the HAMT during traversal. PyHamtObject *old_vars = context_get_current_vars(ctx); Py_INCREF(old_vars); PyHamtObject *new_vars = _PyHamt_Assoc( @@ -899,8 +899,8 @@ contextvar_del(PyContextVar *var) return -1; } - // gh-148891: _PyHamt_Without may allocate nodes, which may trigger GC. - // Bump the reference count to prevent the HAMT from being freed during traversal. + // gh-148891: Make a strong reference so a GC during _PyHamt_Without will + // not free the HAMT during traversal. PyHamtObject *vars = context_get_current_vars(ctx); Py_INCREF(vars); PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var);