Skip to content

Decision needed: GIL hand-off inside array loops (#141 phase 2), (i) OSR-style exit vs (iii) RuntimeError #152

Closed

Description

@b-re-w

#141 phase 1 polls the eval breaker everywhere except while a function holding an array copy-in or an entry-globals snapshot is active: there, user code (other threads, signal handlers) could change the lists it copied. So long numeric array loops still starve other threads (on this app: Kotlin threads calling Python block).

Options:

  • (i) OSR-style exit: at a hand-off point, flush writes, hand off, then if a list's length/element types changed, move the function's state into an interpreter frame and continue interpreted. Exact identity; large work (frame state transfer).
  • (ii) hand off only where no snapshot is held (done in phase 1).
  • (iii) flush, hand off, re-copy; if a length or element type changed, raise a clear RuntimeError. Differs from CPython only for racy programs that resize a list another thread is iterating.

Recommendation (leader): (i), because the user instruction is to guarantee CPython identity. Waiting for the user.

Activity

  1. b-re-w commented on Oct 3, 2026

    @b-re-w
    MemberAuthor

    Re-framed for the user's decision (2026-10-04): free-threaded 3.15t is the only build. There is no GIL to hand off, but the question does not disappear entirely. It moves to the points where a compiled thread detaches.

    What holds: while a snapshot-holding function holds a Py_BEGIN_CRITICAL_SECTION on each list it copied in, from before copy-in to after write-back, no other thread can mutate those lists. CPython allows exactly such an execution: every list operation takes the same per-object lock, so an execution where all of our accesses happen inside one hold and the other threads' list operations fall before or after it is one CPython can produce. Identity holds for that region.

    What does not hold automatically: a critical section is suspended whenever the thread detaches, and it is re-taken after. That covers blocking on another lock, a stop-the-world request (the free-threaded GC stops the world), and the eval-breaker poll that #141 needs so that stop-the-world and signals are not starved. Between the restart and the re-acquire, another thread can change a list's elements, length or element types. So:

    • Polls stay necessary inside long array loops: a thread that never polls stalls every other thread's GC stop-the-world, the free-threaded form of the starvation.
    • At each poll in a snapshot-holding function, after re-acquiring: re-copy the arrays, which is valid because it sees the other threads' writes at that point, as CPython would. Then check that the lengths and exact element types are unchanged.
      • If they are unchanged, continue. Identity holds.
      • If they changed, this issue's choice still applies: (i) an OSR-style exit to the interpreter, or (iii) a clear RuntimeError. A lock around the copy alone does not decide it.
    • Several lists: acquire all the critical sections (Py_BEGIN_CRITICAL_SECTION2 for two; a fixed order beyond that), then copy in. Acquiring a later one can suspend an earlier one, so the copy comes after all are held.

    Proposal: keep this issue open as the decision for the changed-after-poll case. The common case is covered by the lock plus re-validation, so it becomes rare. My recommendation stays (i). (iii) only differs for programs that resize a list while another thread runs a compiled loop over it.

  2. added a commit that references this issue on Oct 25, 2026
  3. b-re-w commented on Oct 25, 2026

    @b-re-w
    MemberAuthor

    Landed in develop via #335 (option i: poll in snapshot holders, re-validate, OSR-style exit to a generated continuation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions