Nursery: document syncAwaitAll's caller io_context requirement for LLMs

This commit is contained in:
2026-06-09 16:48:58 -04:00
parent 656aae37c8
commit d33e70f14a
3 changed files with 26 additions and 4 deletions
+10 -1
View File
@@ -204,7 +204,8 @@ nursery.launch(
nursery.requestCancelOnAll(); nursery.requestCancelOnAll();
nursery.closeAdmission(); nursery.closeAdmission();
nursery.syncAwaitAllSettlements(ioContext); nursery.syncAwaitAllSettlements(
sscl::ComponentThread::getSelf()->getIoContext());
``` ```
Each slot owns a `SyncCancelerForAsyncWork`. `requestCancelOnAll()` only signals Each slot owns a `SyncCancelerForAsyncWork`. `requestCancelOnAll()` only signals
@@ -213,6 +214,14 @@ completion callbacks run. Call `closeAdmission()` explicitly before
`asyncAwaitAllSettlements()` or `syncAwaitAllSettlements()`; those APIs wait until `asyncAwaitAllSettlements()` or `syncAwaitAllSettlements()`; those APIs wait until
all slots have retired naturally and throw if admission is still open. all slots have retired naturally and throw if admission is still open.
`syncAwaitAllSettlements()` runs a nested `io_context` loop on the **calling
thread** (it blocks in `run_one()` until every slot has retired). Pass the
caller thread's `io_context` — usually `ComponentThread::getSelf()->getIoContext()`
— not some other thread's context. While the caller is blocked pumping another
thread's queue, handlers posted to the caller's own `io_context` are abandoned
and the drain can deadlock even when in-flight work has already completed on a
different thread.
`launch(factory, onSettledHook)` registers a non-null hook before `fillSlot()`. `launch(factory, onSettledHook)` registers a non-null hook before `fillSlot()`.
Omit the hook (default `nullptr`) when no settlement callback is needed. Omit the hook (default `nullptr`) when no settlement callback is needed.
`Slot::Lease` is commit-required: an uncommitted lease removes its `Slot::Lease` is commit-required: an uncommitted lease removes its
+12 -3
View File
@@ -45,9 +45,15 @@ struct MemberInvoker : MemberInvokerBase
* SyncCancelerForAsyncWork, and provides drain APIs. * SyncCancelerForAsyncWork, and provides drain APIs.
* *
* Call closeAdmission() explicitly before asyncAwaitAllSettlements() or * Call closeAdmission() explicitly before asyncAwaitAllSettlements() or
* syncAwaitAllSettlements(). syncAwaitAllSettlements() caller must pass the * syncAwaitAllSettlements().
* io_context where non-viral posting completions will land, and must ensure *
* that io_context is prepared to run (e.g. not left stopped without restart). * syncAwaitAllSettlements() runs a nested io_context loop on the calling
* thread (AsynchronousBridge). Pass the calling thread's io_context —
* typically
* ComponentThread::getSelf()->getIoContext() — not another thread's
* io_context. If the caller pumps a different thread's queue while blocked,
* completions posted back to the caller's own io_context are never executed
* and the drain can deadlock even after cooperative cancel.
*/ */
class NonViralTaskNursery class NonViralTaskNursery
{ {
@@ -307,6 +313,9 @@ public:
} }
} }
/** Nested drain: blocks the calling thread in run_one() on @p ioContext until
* all slots retire. @p ioContext must be the caller thread's io_context.
*/
void syncAwaitAllSettlements(boost::asio::io_context &ioContext) void syncAwaitAllSettlements(boost::asio::io_context &ioContext)
{ {
if (admissionIsOpen()) if (admissionIsOpen())
@@ -25,6 +25,10 @@ public:
boost::asio::post(io_context, []{}); boost::asio::post(io_context, []{});
} }
/** Blocks the calling thread in run_one() on the bridge's io_context.
* Used by syncAwaitAllSettlements(); that io_context must be the caller
* thread's own queue so posted completions on the caller are not starved.
*/
void waitForAsyncOperationCompleteOrIoContextStopped(void) void waitForAsyncOperationCompleteOrIoContextStopped(void)
{ {
for (;;) for (;;)