python / python/cpython

Sharing a `contextvars.Context` iterator across threads crashes (HAMT iterator cursor corruption) under free-threading

Đang mở
#154,535 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

interpreter-core topic-free-threading type-crash
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

Crash report

What happened?

On the free-threaded build, advancing a single shared iterator over a contextvars.Context from multiple threads corrupts the iterator's traversal cursor and dereferences a garbage node pointer → SIGSEGV.

The HAMT iterator keeps its whole cursor — i_nodes[] (borrowed node pointers), i_pos[], i_level — inside the iterator object and advances it with plain reads/writes and no critical section (Python/hamt.c, main@a1d580430c8):

static hamt_iter_t
hamt_iterator_next(PyHamtIteratorState *iter, PyObject **key, PyObject **val) {
    if (iter->i_level < 0)                       /* :2188  plain read of the cursor level */
        return I_END;
    PyHamtNode *current = iter->i_nodes[iter->i_level];   /* :2194  read node at current level */
    if (IS_BITMAP_NODE(current)) {               /* :2196  Py_TYPE(current) -> SEGV on a wild node */
        return hamt_iterator_bitmap_next(iter, key, val);
    }
    ...
}

static hamt_iter_t
hamt_iterator_bitmap_next(PyHamtIteratorState *iter, ...) {
    int8_t level = iter->i_level;
    PyHamtNode_Bitmap *node = (PyHamtNode_Bitmap *)(iter->i_nodes[level]);
    if (pos + 1 >= Py_SIZE(node)) {              /* :2092  Py_SIZE(node) -> SEGV on a wild node */
        iter->i_level--;                         /* :2097  plain write of the cursor level */
        return hamt_iterator_next(iter, key, val);
    }
    ...
}

Two threads calling next() on the same iterator desync i_level against i_nodes[], so a thread reads current = iter->i_nodes[iter->i_level] as a stale / NULL / wild pointer and immediately evaluates IS_BITMAP_NODE(current) = Py_TYPE(current) (or Py_SIZE(node) in hamt_iterator_bitmap_next) — dereferencing garbage. Because the nodes in i_nodes are held borrowed (hamt_iterator_init: "we don't incref/decref nodes in i_nodes"), a concurrent structural change is not even required — cursor desync alone produces the wild dereference. This is safe under the GIL (one thread runs the advance at a time).

The HAMT iterator backs contextvars.Context iteration — iter(ctx), ctx.keys(), ctx.values(), ctx.items() — via hamt_baseiter_tp_iternext (hamt.c:2479).

Reproducer

Free-threaded build, PYTHON_GIL=0:

import contextvars, threading
NT = 8; ITERS = 40000
vs = [contextvars.ContextVar(f"v{i}") for i in range(16)]
ctx = contextvars.copy_context()
def populate():
    for i, v in enumerate(vs):
        v.set(i)
ctx.run(populate)
cell = [iter(ctx)]
def worker():
    for _ in range(ITERS):
        it = cell[0]
        try: next(it)
        except StopIteration: cell[0] = iter(ctx)
        except Exception: pass
ts = [threading.Thread(target=worker) for _ in range(NT)]
for t in ts: t.start()
for t in ts: t.join()
  • 6/6 SIGSEGV on a plain free-threaded debug build (no sanitizer).
  • On a free-threaded ASan build:
AddressSanitizer: SEGV in _Py_TYPE_impl  Include/object.h:234
  hamt_iterator_bitmap_next  Python/hamt.c:2092
  hamt_iterator_next         Python/hamt.c:2196
  hamt_baseiter_tp_iternext  Python/hamt.c:2479
  context_run                Python/context.c:731
  • Under TSan: WARNING: ThreadSanitizer: data race … Python/hamt.c:2188 in hamt_iterator_next.

The crash is not Py_DEBUG-only — it reproduces as a raw SIGSEGV from the wild-pointer dereference on the plain free-threaded build.

Relationship to gh-124397

Per gh-124397 ("Strategy for Iterators in Free Threading") point 3, C iterators should get "the minimal changes necessary to cause them to not crash … Concurrent access is allowed to return duplicate values, skip values, or raise an exception." The HAMT / Context iterator was never brought under that hardening, so it crashes rather than returning dup/skip. It is the contextvars.Context sibling of the already-tracked shared-iterator crashes: dict (#154130), set (#144357), and memoryview.

Suggested fix

Bring the HAMT iterator advance under a per-iterator critical section (or make the i_level/i_pos/i_nodes cursor accesses atomic with a NULL/bounds check on the borrowed i_nodes[i_level] before dereferencing), matching the dictiter/setiter hardening. The guard must span the whole hamt_iterator_next*_bitmap_next / *_array_next descent because i_nodes holds borrowed pointers.

Found with fusil's --tsan mode; reproducer reduced and this report drafted with AI assistance (Claude Code), then verified by hand on free-threaded debug / ASan / TSan builds of main.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.16.0a0 free-threading build (heads/main:a1d580430c8, Jul 18 2026, 20:23:36) [Clang 21.1.8 (6ubuntu1)]

Linked PRs
  • gh-154586

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong Python/hamt.c, tại hamt_iterator_next, hamt_iterator_bitmap_next và hamt_baseiter_tp_iternext, sau đó tái hiện lỗi bằng free-threaded Context iterator script được cung cấp. So sánh với hardening được mô tả cho các iterator của dict và set. Được xem là hoàn tất khi việc tiến hành đồng thời không còn gây crash hoặc dereference các borrowed nodes không hợp lệ, và các free-threaded checks liên quan đều vượt qua.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
c, python
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.