@@ -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,48 @@ 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()
350
352
v = await f
351
353
await current_task.acquire()
352
354
return v
353
355
async def do_call ():
354
- await callee(on_block)
355
- if not blocked .done():
356
- blocked .set_result(False )
356
+ result = await callee(* args, on_block)
357
+ if not blocked_or_result .done():
358
+ blocked_or_result .set_result(result )
357
359
else :
358
360
current_task.release()
359
361
asyncio.create_task(do_call())
360
- return await blocked
362
+ return await blocked_or_result
361
363
```
362
364
Talking through this little Python pretzel of control flow:
363
365
1 . ` call_and_handle_blocking ` starts by running ` do_call ` in a fresh Python
364
366
task and then immediately ` await ` ing a future that will be resolved by
365
367
` do_call ` . Since ` current_task ` isn't ` release() ` d or ` acquire() ` d as part
366
368
of this process, the net effect is to directly transfer control flow from
367
369
` call_and_handle_blocking ` to ` do_call ` task without allowing other tasks to
368
- run (as if by ` cont.new ` + ` resume ` in [ stack-switching] ).
370
+ run (as if by the ` cont.new ` + ` resume ` instructions of [ stack-switching] ).
369
371
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
372
- 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] ).
372
+ Canonical ABI ensures will be called whenever there is a need to block on
373
+ I/O (represented by the future ` f ` ).
374
+ 3 . If ` on_block ` is called, the first time it is called it will signal that
375
+ the ` callee ` has ` Blocked ` before ` await ` ing the future. Because the
376
+ ` current_task ` lock is not ` release() ` d , control flow is transferred
377
+ directly from ` on_block ` to ` call_and_handle_blocking ` without allowing any
378
+ other tasks to execute (as if by the ` suspend ` instruction of
379
+ [ stack-switching] ).
376
380
4 . If ` on_block ` is called more than once, there is no longer a caller to
377
- directly switch to, so the ` current_task ` lock is ` release() ` d, just like
378
- in ` default_on_block ` , so that the Python async scheduler can pick another
379
- task to switch to.
381
+ directly switch to, so the ` current_task ` lock is ` release() ` d so that the
382
+ Python async scheduler can pick another task to switch to.
380
383
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 .
384
+ resolves ` blocking ` to the (not- ` Blocking ` ) return value of ` callee ` .
382
385
383
386
With these tricky primitives defined, the rest of the logic below can simply
384
387
use ` on_block ` when there is a need to block and ` call_and_handle_blocking `
@@ -616,7 +619,7 @@ tree.
616
619
class Subtask (CallContext ):
617
620
ft: FuncType
618
621
flat_args: CoreValueIter
619
- flat_results: Optional[list[any ]]
622
+ flat_results: Optional[list[Any ]]
620
623
state: CallState
621
624
lenders: list[ResourceHandle]
622
625
notify_supertask: bool
@@ -2147,25 +2150,25 @@ async def canon_lower(opts, ft, callee, task, flat_args):
2147
2150
async def do_call (on_block ):
2148
2151
await callee(task, subtask.on_start, subtask.on_return, on_block)
2149
2152
[] = 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 ]
2153
+ match await call_and_handle_blocking(do_call):
2154
+ case Blocked():
2155
+ subtask.notify_supertask = True
2156
+ task.need_to_drop += 1
2157
+ i = task.inst.async_subtasks.add(subtask)
2158
+ flat_results = [pack_async_result(i, subtask.state)]
2159
+ case None :
2160
+ flat_results = [0 ]
2157
2161
return flat_results
2158
2162
```
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:
2163
+ In the asynchronous case, if ` do_call ` blocks before ` Subtask.finish `
2164
+ (signalled by ` callee ` calling ` on_block ` ), the ` Subtask ` is added to an
2165
+ instance-wide table and given an ` i32 ` index that is later returned by
2166
+ ` task.wait ` to signal subtask's progress. The ` need_to_drop ` increment is
2167
+ matched by a decrement in ` canon_subtask_drop ` and ensures that all subtasks
2168
+ of a supertask are allowed to complete before the supertask completes. The
2169
+ ` notify_supertask ` flag is set to tell ` Subtask ` methods (below) to
2170
+ asynchronously notify the supertask of progress. Lastly, the current progress
2171
+ of the subtask is returned to the caller, packed with the ` i32 ` subtask index:
2169
2172
``` python
2170
2173
def pack_async_result (i , state ):
2171
2174
assert (0 < i < 2 ** 30 )
0 commit comments