feat: add green threads (cooperative concurrency) with spawn, Channel, and async I/O#10
Merged
zombocoder merged 10 commits intoApr 30, 2026
Merged
Conversation
Owner
|
This is a super draft implementation, pls folow the code style guidelines and add more tests and examples |
1c18c5c to
b25d9fb
Compare
…, and async I/O Introduces cooperative multitasking to O²L: - `spawn <expr>` keyword: launches a new coroutine that runs cooperatively within the same OS thread (SpawnNode AST node, Lexer/Parser support) - `Scheduler` (singleton run-loop): drives coroutines via a ready_queue_, timer_queue_, suspended_ map, and thread-safe IO completion queue - `Coroutine`: owns body ASTNode + cloned Context; carries state (Ready/Running/Suspended/Completed/Failed) with block_resume_stack for nested block resumption - `ChannelInstance`: buffered/unbuffered channel for send/receive between coroutines; senders suspend when full, receivers suspend when empty - `ConcurrencyLibrary`: exposes `Channel.new(capacity)` and `io.sleep(ms)` / `io.yield()` to O²L programs - `IOThreadPool`: fixed thread pool offloading blocking I/O; uses thread-safe `enqueueReady` to post completions back to the scheduler - `Context::clone()`: deep-copies variable bindings for coroutine isolation - `CArray.fromList()`: copies a ListInstance into a CArrayInstance for FFI - BlockNode/WhileStatementNode: use block_resume_stack for correct nested coroutine resumption - Cross-platform: MSVC Value variant alias, unistd.h for macOS/Linux, holds_Int_Value/holds_Long_Value helpers, Windows socket defines All existing tests pass; adds 9 new concurrency test suites.
b25d9fb to
0023bd1
Compare
…lures - Parser: accept keyword 'new' as method name after '.' so Channel.new() parses correctly (fixes ConcurrencyTest.channel_basic and ConcurrencyTest.concurrency_advanced) - Scheduler: store root coroutine exception in root_exception_ and expose it via getRootException(); clear on reset() - Interpreter: rethrow root coroutine exception after scheduler.run() so EvaluationErrors propagate to callers (fixes IntegrationTest DemoAccessError and ErrorHandling)
…plementation - Fix YieldNode ODR violation between test_resume_logic.cpp and test_suspend_exception.cpp by wrapping each in anonymous namespaces (root cause of MSVC 0xc0000005 crash — linker picked wrong vtable) - Fix YieldNode to check hasResumeValue() before yielding to prevent infinite re-suspension on resume - Remove debug cerr prints from BlockNode, WhileStatementNode, Scheduler - Remove unused <iostream> includes from BlockNode and WhileStatementNode - Use EXPECT_EQ instead of assert in test_resume_logic - Parser: simplify Channel.new() member name token handling - Context::clone(): preserve this_stack_ for spawn inside object methods
- yield_in_false_loop: while(false) body never executes yield - yield_single_iteration: loop terminates on first resume (condition false) - yield_deeply_nested: 3 levels of BlockNode nesting exercises block_resume_stack depth
- root_exception_captured: root coroutine error stored in root_exception_, rethrowable - non_root_exception_ignored: non-root errors don't set root_exception_ - root_exception_null_on_success: successful root leaves root_exception_ null
…mption - error_after_resume: yield then throw on resume, verify root_exception_ captured - scheduler_clean_after_run: isActive()==false after success, error, and yield+error - error_in_condition_after_resume: condition throws after yield-resume cycle completes
- yield_nested_inner_loop: yield in inner while-while, 4 yields across 2*2 iterations - yield_nested_outer_body: yield in outer body after inner loop completes, 2 yields Both exercise block_resume_stack at 4+ nesting levels
- channel_producer_consumer_values: producer sends 3 values via channel, consumer sums them - three_coroutine_round_robin: 3 coroutines yield to shared log, verify deterministic order
- channel_multiple_producers: 2 producers send to one buffered channel - channel_multiple_consumers: 2 consumers receive from one buffered channel - channel_unbuffered_blocking: sender blocks until receiver is ready - channel_buffered_full: sender blocks when buffer at capacity
- suspend_for_io_basic: IO lambda result delivered to coroutine on resume - suspend_for_io_different_thread: IO work runs on a background thread - suspend_for_io_multiple: 3 concurrent IO suspensions all complete correctly
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.
Closes #9
Summary
spawn <expr>keyword to launch cooperative coroutines within the same OS threadSchedulersingleton run-loop with ready/timer/suspended queues and correct exception propagation from the root coroutineChannelInstanceprovides buffered/unbuffered send/receive with suspend-on-full / suspend-on-empty semanticsIOThreadPooloffloads blocking work to background threads; coroutines resume viaScheduler::resumeCoroutineContext::clone()isolates each coroutine's variable bindings from the spawning scopeConcurrencyLibraryexposesChannel.new(capacity),io.sleep(ms),io.yield()to O²L programsCArray.fromList()completes the FFI bridge for async callback useTest plan
test_scheduler.cpp— run-loop, multi-coroutine scheduling, exception propagationtest_spawn_basic.cpp—spawnkeyword end-to-endtest_channel_basic.cpp— send/receive, buffering, blockingtest_resume_logic.cpp—suspendCurrent/resumeCoroutineround-triptest_io_yield.cpp—yieldandsleepintegrationtest_suspend_exception.cpp— exception propagation through coroutinestest_context_clone.cpp— context isolation between coroutinestest_carray_from_list.cpp—CArray.fromList()correctness