You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decision needed: GIL hand-off inside array loops (#141 phase 2), (i) OSR-style exit vs (iii) RuntimeError #152
#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.
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.
Activity
b-re-w commentedon Oct 3, 2026
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_SECTIONon 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:
Py_BEGIN_CRITICAL_SECTION2for 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.
Merge pull request #335 from thisisthepy/feat/tp-osr-exit
b-re-w commentedon Oct 25, 2026
Landed in develop via #335 (option i: poll in snapshot holders, re-validate, OSR-style exit to a generated continuation).