Fix a circular reference leak in CoroTaskRunner::expectEarlyExit().
When postCoroTask() fails to post, the coroutine frame holds a
shared_ptr back to the CoroTaskRunner, creating an unreachable
cycle. Breaking the cycle by destroying the task in expectEarlyExit()
fixes the leak.
Add 3 tests for CoroTask<T> (value-returning coroutines):
- testValueReturn: inner coroutine returns int via co_return
- testValueException: exception propagation from inner coroutine
- testValueChaining: nested value-returning coroutine chain
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce the core building blocks for migrating from Boost.Coroutine to
C++20 stackless coroutines (Milestone 1):
- CoroTask<T>: RAII coroutine return type with promise_type, symmetric
transfer via FinalAwaiter, and lazy start (suspend_always)
- CoroTaskRunner: Lifecycle manager (nested in JobQueue) mirroring the
existing Coro class — handles LocalValues swap, nSuspend_ accounting,
mutex-guarded resume, and join/post semantics
- JobQueueAwaiter: Convenience awaiter combining suspend + auto-repost,
with graceful fallback when JobQueue is stopping
- postCoroTask(): JobQueue entry point for launching C++20 coroutines
- CoroTask_test.cpp: 8 unit tests covering completion, suspend/resume
ordering, LocalValue isolation, exception propagation, and shutdown
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>