@@ -304,7 +304,7 @@ class EventCode(IntEnum):
304304
305305EventTuple = tuple[EventCode, int ]
306306EventCallback = Callable[[], EventTuple]
307- OnBlockCallback = Callable[[Awaitable], any ]
307+ OnBlockCallback = Callable[[Awaitable], Any ]
308308```
309309The ` CallState ` enum describes the linear sequence of states that an async call
310310necessarily transitions through: [ ` STARTING ` ] ( Async.md#backpressure ) , ` STARTED ` ,
@@ -340,45 +340,51 @@ async def default_on_block(f):
340340 await current_task.acquire()
341341 return v
342342
343- async def call_and_handle_blocking (callee ):
344- blocked = asyncio.Future()
343+ class Blocked : pass
344+
345+ async def call_and_handle_blocking (callee , * args ) -> Blocked| Any:
346+ blocked_or_result = asyncio.Future[Blocked| Any]()
345347 async def on_block (f ):
346- if not blocked .done():
347- blocked .set_result(True )
348+ if not blocked_or_result .done():
349+ blocked_or_result .set_result(Blocked() )
348350 else :
349351 current_task.release()
352+ assert (not f.done())
350353 v = await f
351354 await current_task.acquire()
352355 return v
353356 async def do_call ():
354- await callee(on_block)
355- if not blocked .done():
356- blocked .set_result(False )
357+ result = await callee(* args, on_block)
358+ if not blocked_or_result .done():
359+ blocked_or_result .set_result(result )
357360 else :
358361 current_task.release()
359362 asyncio.create_task(do_call())
360- return await blocked
363+ return await blocked_or_result
361364```
362365Talking through this little Python pretzel of control flow:
3633661 . ` call_and_handle_blocking ` starts by running ` do_call ` in a fresh Python
364367 task and then immediately ` await ` ing a future that will be resolved by
365368 ` do_call ` . Since ` current_task ` isn't ` release() ` d or ` acquire() ` d as part
366369 of this process, the net effect is to directly transfer control flow from
367370 ` call_and_handle_blocking ` to ` do_call ` task without allowing other tasks to
368- run (as if by ` cont.new ` + ` resume ` in [ stack-switching] ).
371+ run (as if by the ` cont.new ` + ` resume ` instructions of [ stack-switching] ).
3693722 . ` do_call ` passes the local ` on_block ` closure to ` callee ` , which the
370- Canonical ABI ensures will be called whenever there is a need to block.
371- 3 . If ` on_block ` is called, the first time it resolves ` blocking ` . Because
373+ Canonical ABI ensures will be called whenever there is a need to block on
374+ I/O (represented by the future ` f ` ).
375+ 3 . If ` on_block ` is called, the first time it is called it will signal that
376+ the ` callee ` has ` Blocked ` before ` await ` ing the unresolved future. Because
372377 the ` current_task ` lock is not ` release() ` d or ` acquire() ` d as part of this
373- process, the net effect is to directly transfer control flow from ` do_call `
374- back to ` call_and_handle_blocking ` without allowing other tasks to run (as
375- if by ` suspend ` in [ stack-switching] ).
378+ process, the net effect is to transfer control flow directly from
379+ ` on_block ` to ` call_and_handle_blocking ` without allowing any other tasks
380+ to execute (as if by the ` suspend ` instruction of [ stack-switching] ).
3763814 . If ` on_block ` is called more than once, there is no longer a caller to
377382 directly switch to, so the ` current_task ` lock is ` release() ` d, just like
378383 in ` default_on_block ` , so that the Python async scheduler can pick another
379384 task to switch to.
3803855 . If ` do_call ` finishes without ` on_block ` ever having been called, it
381- resolves ` blocking ` to ` False ` to communicate this fact to the caller.
386+ resolves ` blocking ` to the (not-` Blocking ` ) return value of ` callee ` to
387+ communicate this fact to the caller.
382388
383389With these tricky primitives defined, the rest of the logic below can simply
384390use ` on_block ` when there is a need to block and ` call_and_handle_blocking `
@@ -616,7 +622,7 @@ tree.
616622class Subtask (CallContext ):
617623 ft: FuncType
618624 flat_args: CoreValueIter
619- flat_results: Optional[list[any ]]
625+ flat_results: Optional[list[Any ]]
620626 state: CallState
621627 lenders: list[ResourceHandle]
622628 notify_supertask: bool
@@ -2147,25 +2153,25 @@ async def canon_lower(opts, ft, callee, task, flat_args):
21472153 async def do_call (on_block ):
21482154 await callee(task, subtask.on_start, subtask.on_return, on_block)
21492155 [] = subtask.finish()
2150- if await call_and_handle_blocking(do_call):
2151- subtask.notify_supertask = True
2152- task.need_to_drop += 1
2153- i = task.inst.async_subtasks.add(subtask)
2154- flat_results = [pack_async_result(i, subtask.state)]
2155- else :
2156- flat_results = [0 ]
2156+ match await call_and_handle_blocking(do_call):
2157+ case Blocked():
2158+ subtask.notify_supertask = True
2159+ task.need_to_drop += 1
2160+ i = task.inst.async_subtasks.add(subtask)
2161+ flat_results = [pack_async_result(i, subtask.state)]
2162+ case None :
2163+ flat_results = [0 ]
21572164 return flat_results
21582165```
2159- In the asynchronous case, ` Task.call_and_handle_blocking ` returns ` True ` if the
2160- call to ` do_call ` blocks. In this blocking case, the ` Subtask ` is added to
2161- stored in an instance-wide table and given an ` i32 ` index that is later
2162- returned by ` task.wait ` to indicate that the subtask made progress. The
2163- ` need_to_drop ` increment is matched by a decrement in ` canon_subtask_drop ` and
2164- ensures that all subtasks of a supertask are allowed to complete before the
2165- supertask completes. The ` notify_supertask ` flag is set to tell ` Subtask `
2166- methods (below) to asynchronously notify the supertask of progress. Lastly,
2167- the current state of the subtask is eagerly returned to the caller, packed
2168- with the ` i32 ` subtask index:
2166+ In the asynchronous case, if ` do_call ` blocks before ` Subtask.finish `
2167+ (signalled by ` callee ` calling ` on_block ` ), the ` Subtask ` is added to an
2168+ instance-wide table and given an ` i32 ` index that is later returned by
2169+ ` task.wait ` to signal subtask's progress. The ` need_to_drop ` increment is
2170+ matched by a decrement in ` canon_subtask_drop ` and ensures that all subtasks
2171+ of a supertask are allowed to complete before the supertask completes. The
2172+ ` notify_supertask ` flag is set to tell ` Subtask ` methods (below) to
2173+ asynchronously notify the supertask of progress. Lastly, the current progress
2174+ of the subtask is returned to the caller, packed with the ` i32 ` subtask index:
21692175``` python
21702176def pack_async_result (i , state ):
21712177 assert (0 < i < 2 ** 30 )
0 commit comments