@@ -304,7 +304,7 @@ class EventCode(IntEnum):
304
304
305
305
EventTuple = tuple[EventCode, int ]
306
306
EventCallback = Callable[[], EventTuple]
307
- OnBlockCallback = Callable[[Awaitable], any ]
307
+ OnBlockCallback = Callable[[Awaitable], Any ]
308
308
```
309
309
The ` CallState ` enum describes the linear sequence of states that an async call
310
310
necessarily transitions through: [ ` STARTING ` ] ( Async.md#backpressure ) , ` STARTED ` ,
@@ -340,45 +340,51 @@ async def default_on_block(f):
340
340
await current_task.acquire()
341
341
return v
342
342
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]()
345
347
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() )
348
350
else :
349
351
current_task.release()
352
+ assert (not f.done())
350
353
v = await f
351
354
await current_task.acquire()
352
355
return v
353
356
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 )
357
360
else :
358
361
current_task.release()
359
362
asyncio.create_task(do_call())
360
- return await blocked
363
+ return await blocked_or_result
361
364
```
362
365
Talking through this little Python pretzel of control flow:
363
366
1 . ` call_and_handle_blocking ` starts by running ` do_call ` in a fresh Python
364
367
task and then immediately ` await ` ing a future that will be resolved by
365
368
` do_call ` . Since ` current_task ` isn't ` release() ` d or ` acquire() ` d as part
366
369
of this process, the net effect is to directly transfer control flow from
367
370
` 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] ).
369
372
2 . ` 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
372
377
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] ).
376
381
4 . If ` on_block ` is called more than once, there is no longer a caller to
377
382
directly switch to, so the ` current_task ` lock is ` release() ` d, just like
378
383
in ` default_on_block ` , so that the Python async scheduler can pick another
379
384
task to switch to.
380
385
5 . 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.
382
388
383
389
With these tricky primitives defined, the rest of the logic below can simply
384
390
use ` on_block ` when there is a need to block and ` call_and_handle_blocking `
@@ -616,7 +622,7 @@ tree.
616
622
class Subtask (CallContext ):
617
623
ft: FuncType
618
624
flat_args: CoreValueIter
619
- flat_results: Optional[list[any ]]
625
+ flat_results: Optional[list[Any ]]
620
626
state: CallState
621
627
lenders: list[ResourceHandle]
622
628
notify_supertask: bool
@@ -2147,25 +2153,25 @@ async def canon_lower(opts, ft, callee, task, flat_args):
2147
2153
async def do_call (on_block ):
2148
2154
await callee(task, subtask.on_start, subtask.on_return, on_block)
2149
2155
[] = 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 ]
2157
2164
return flat_results
2158
2165
```
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:
2169
2175
``` python
2170
2176
def pack_async_result (i , state ):
2171
2177
assert (0 < i < 2 ** 30 )
0 commit comments