Repository navigation
Conversation
docs/greenthread.md proposes several Python threads in one context on one operating-system thread: parked threads switch on the heap, pinned ones keep their C frames and run only from the top of the C stack; host functions answer later through tickets; RuntimeError instead of a hang when nothing can run. It starts after calls stop recursing in C. tests/tla/SpyGreenThreads.tla checks the scheduler: 59,003 states, no error (tla_check.py --only, in a Linux container). TLC rejects resuming a thread under the top, freeing a lock that has waiters, preempting a pinned thread, and leaving out the RuntimeError. make docs and tla_coverage.py pass. Nothing in spy.c changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj
stdlib/_thread.c runs each Python thread on a system thread of its own; threads take turns at one context through FIFO tickets under one mutex. spy links it (stdlib/lib_thread.a); hosts load stdlib/_thread.so. The core gains spy_thread_new/swap/free, which move one thread's error, frames, handled exception, stack measure and call depth, and spy_set_switch, polled with the interrupt and run at the next tick so no builtin operation interleaves. The green-thread proposal becomes docs/threads.md; its model is replaced by SpyThreads.tla. make test passes; tests/std.py: _thread probe matches CPython 3.15 (40 lines). SpyThreads.tla: 25,665 states, no error; TLC rejects 7 planted bugs. Sanitizers and measurements follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj
ThreadSanitizer saw link_table's compare-and-swap write the table's api field while another thread of a context read it; an already linked table is now only loaded. TSan and ASan+UBSan clean on tests/threads, the _thread probe and tests/api. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj
lock.acquire() and RLock.acquire() with no arguments wait for ever without going through _timeout: a free lock's acquire and release take 0.47 us, down from 2.15 us. docs/benchmark.md gains a Threads section: import 0.51 ms, start and join 27.4 us, a turn hand-off 3.66 us, and start-up +2.3% on macOS from linking _thread (dyld binding its pthread imports), +0.7% on Linux, +0.57% instructions. make test passes; the _thread probe matches CPython (40 lines). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj
Every thread runs on the host's system thread and switches only where it would block: a blocked thread runs the queued threads on top of itself until what it waits for holds; with none left a timed wait times out and a wait for ever raises RuntimeError. No preemption. The module is Python (stdlib/_thread.py) embedded by tools/build_intrinsics.py into a C wrapper, still linked statically or loaded as _thread.so; spy.c and spy.h are back to main, and nothing needs -pthread. make test passes: the _thread probe matches CPython (40 lines), tests/thread_virtual.py passes 14 checks, tests/threads 4 contexts. SpyThreads.tla: 71,820 states, no error; three planted bugs caught. Start-up -0.4% wall on macOS (control -0.0%), +0.12% instructions; start and join 3.80 us (was 27.4), a free lock 0.51 us. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_threadas virtual threads, in a newstdlib/directory. The module can be linked statically or loaded as a shared library. It uses no system threads, andspy.candspy.hare unchanged from main.What
stdlib/holds the C modules of the standard library, one source file each in the native-module form.makebuildsstdlib/libNAME.aandstdlib/NAME.so.stdlib/spy-stdlib.hdeclares each module's init function.spylinks every module;spycaccepts either the.cor the.afile._threadprovides CPython 3.15's interface for whatthreadingneeds, with CPython's error messages:start_new_threadandstart_joinable_threadwith_ThreadHandle;allocate_lock,lockandRLock;get_ident,_count,stack_sizeand_shutdown, among others.RuntimeErrorinstead of hanging.docs/threads.mdsection 2 lists every case that differs from CPython.stdlib/_thread.py.tools/build_intrinsics.pyembeds it into the generatedstdlib/_thread-source.h.stdlib/_thread.cis a 24-line wrapper that runs that source.spy-std.c: once a native module's link table is linked, it is only read. ThreadSanitizer reported a race when several contexts loaded the same module at once.docs/threads.md;stdlib/;docs/benchmark.md.Evidence
make testpasses, including spec 419/419.tests/std.py:tests/thread_probe.pyprints the same 40 lines under spy and under the pinned CPython 3.15.tests/thread_virtual.pypasses 14 spy-only checks: start order, no preemption, idents, nested waits resuming last first, theRuntimeErrorcases, and a thread that cannot wait for one below it.tests/threadsloads_thread.sointo four contexts on four system threads at once. Each context runs virtual threads and is freed while one is still queued.TLC on
tests/tla/SpyThreads.tla(3 workers, budget 3): 71,820 distinct states, no error. Checked invariants:_currentnames it;Three planted bugs were each caught: a wait that hangs, a wrong ident on resume, and a wait that ends with its condition unmet.
make docs,make check-intrinsicsandtests/tla_coverage.pypass.make coverageon the earlier revision covered every engine line.spy.cis now identical to main, andcli.cchanges by one line.Local CI (
tools/run_workflow.py, from a fresh clone): thetestjob (gcc and clang, withmake sanitize),standardandarchivepass.Windows and macOS x86_64 are covered only by this PR's CI.
Cost
Measured on an M2 with macOS 26.5 and
-O2; details indocs/benchmark.md._threadspygrows by 16.6 KB, mostly the embedded source.Open
stdlib/and stdlib parity, but leaves that rule unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bq79MgongGBn3NvB5dYBoj