@@ -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