@@ -394,21 +394,19 @@ class Task(CallContext):
394
394
caller: Optional[Task]
395
395
on_return: Optional[Callable]
396
396
on_block: OnBlockCallback
397
- borrow_count : int
397
+ need_to_drop : int
398
398
events: list[EventCallback]
399
399
has_events: asyncio.Event
400
- num_async_subtasks: int
401
400
402
401
def __init__ (self , opts , inst , ft , caller , on_return , on_block ):
403
402
super ().__init__ (opts, inst, self )
404
403
self .ft = ft
405
404
self .caller = caller
406
405
self .on_return = on_return
407
406
self .on_block = on_block
408
- self .borrow_count = 0
407
+ self .need_to_drop = 0
409
408
self .events = []
410
409
self .has_events = asyncio.Event()
411
- self .num_async_subtasks = 0
412
410
```
413
411
The fields of ` Task ` are introduced in groups of related ` Task ` methods next.
414
412
Using a conservative syntactic analysis of the component-level definitions of
@@ -570,34 +568,6 @@ external I/O (as emulated in the Python code by awaiting `sleep(0)`:
570
568
await self .wait_on(asyncio.sleep(0 ))
571
569
```
572
570
573
- All ` Task ` s (whether lifted ` async ` or not) are allowed to call ` async ` -lowered
574
- imports. Calling an ` async ` -lowered import stores a ` Subtask ` (defined below)
575
- in the current component instance's ` async_subtasks ` table. The current task
576
- tracks the number of live async subtasks and guards this to be in ` Task.exit `
577
- (below) to ensure [ structured concurrency] .
578
- ``` python
579
- def add_async_subtask (self , subtask ):
580
- assert (subtask.task is self and not subtask.notify_supertask)
581
- subtask.notify_supertask = True
582
- self .num_async_subtasks += 1
583
- return self .inst.async_subtasks.add(subtask)
584
- ```
585
- The ` notify_supertask ` flag signals to the methods of ` Subtask ` (defined below)
586
- to notify this ` Task ` when the async call makes progress.
587
-
588
- The ` borrow_count ` field is used by the following methods to track the number
589
- of borrowed handles that were passed as parameters to the export that have not
590
- yet been dropped (and thus might dangle if the caller destroys the resource
591
- after this export call finishes):
592
- ``` python
593
- def create_borrow (self ):
594
- self .borrow_count += 1
595
-
596
- def drop_borrow (self ):
597
- assert (self .borrow_count > 0 )
598
- self .borrow_count -= 1
599
- ```
600
-
601
571
The ` return_ ` method is called by either ` canon_task_return ` or ` canon_lift `
602
572
(both defined below) to lift and return results to the caller using the
603
573
` on_return ` callback that was supplied by the caller to ` canon_lift ` . Using a
@@ -619,15 +589,16 @@ more than once which must be checked by `return_` and `exit`.
619
589
```
620
590
621
591
Lastly, when a task exits, the runtime enforces the guard conditions mentioned
622
- above and allows a pending task to start.
592
+ above and allows a pending task to start. The ` need_to_drop ` counter is
593
+ incremented and decremented below as a way of ensuring that a task does
594
+ something (like dropping a resource or subtask handle) before the task exits.
623
595
``` python
624
596
def exit (self ):
625
597
assert (current_task.locked())
626
598
assert (not self .events)
627
599
assert (self .inst.num_tasks >= 1 )
628
600
trap_if(self .on_return)
629
- trap_if(self .borrow_count != 0 )
630
- trap_if(self .num_async_subtasks != 0 )
601
+ trap_if(self .need_to_drop != 0 )
631
602
self .inst.num_tasks -= 1
632
603
if self .opts.sync:
633
604
assert (not self .inst.interruptible.is_set())
@@ -728,9 +699,9 @@ called).
728
699
self .flat_results = lower_flat_values(self , max_flat, vs, ts, self .flat_args)
729
700
```
730
701
731
- Lastly, when a ` Subtask ` finishes, it calls ` release_lenders ` to allow owned
732
- handles passed to this subtask to be dropped. In the synchronous or eager case
733
- this happens immediately before returning to the caller. In the
702
+ When a ` Subtask ` finishes, it calls ` release_lenders ` to allow owned handles
703
+ passed to this subtask to be dropped. In the synchronous or eager case this
704
+ happens immediately before returning to the caller. In the
734
705
asynchronous+blocking case, this happens right before the ` CallState.DONE `
735
706
event is delivered to the guest program.
736
707
``` python
@@ -744,6 +715,14 @@ event is delivered to the guest program.
744
715
return self .flat_results
745
716
```
746
717
718
+ Lastly, after a ` Subtask ` has finished and notified its supertask (thereby
719
+ clearing ` enqueued ` ), it may be dropped from the ` async_subtasks ` table:
720
+ ``` python
721
+ def drop (self ):
722
+ trap_if(self .enqueued)
723
+ trap_if(self .state != CallState.DONE )
724
+ self .task.need_to_drop -= 1
725
+ ```
747
726
748
727
### Despecialization
749
728
@@ -1568,7 +1547,9 @@ def pack_flags_into_int(v, labels):
1568
1547
```
1569
1548
1570
1549
Finally, ` own ` and ` borrow ` handles are lowered by initializing new handle
1571
- elements in the current component instance's handle table:
1550
+ elements in the current component instance's handle table. The increment of
1551
+ ` need_to_drop ` is complemented by a decrement in ` canon_resource_drop ` and
1552
+ ensures that all borrowed handles are dropped before the end of the task.
1572
1553
``` python
1573
1554
def lower_own (cx , rep , t ):
1574
1555
h = ResourceHandle(rep, own = True )
@@ -1579,7 +1560,7 @@ def lower_borrow(cx, rep, t):
1579
1560
if cx.inst is t.rt.impl:
1580
1561
return rep
1581
1562
h = ResourceHandle(rep, own = False , scope = cx)
1582
- cx.create_borrow()
1563
+ cx.need_to_drop += 1
1583
1564
return cx.inst.resources.add(t.rt, h)
1584
1565
```
1585
1566
The special case in ` lower_borrow ` is an optimization, recognizing that, when
@@ -1588,6 +1569,7 @@ type, the only thing the borrowed handle is good for is calling
1588
1569
` resource.rep ` , so lowering might as well avoid the overhead of creating an
1589
1570
intermediate borrow handle.
1590
1571
1572
+
1591
1573
### Flattening
1592
1574
1593
1575
With only the definitions above, the Canonical ABI would be forced to place all
@@ -2166,16 +2148,24 @@ async def canon_lower(opts, ft, callee, task, flat_args):
2166
2148
await callee(task, subtask.on_start, subtask.on_return, on_block)
2167
2149
[] = subtask.finish()
2168
2150
if await call_and_handle_blocking(do_call):
2169
- i = task.add_async_subtask(subtask)
2151
+ subtask.notify_supertask = True
2152
+ task.need_to_drop += 1
2153
+ i = task.inst.async_subtasks.add(subtask)
2170
2154
flat_results = [pack_async_result(i, subtask.state)]
2171
2155
else :
2172
2156
flat_results = [0 ]
2173
2157
return flat_results
2174
2158
```
2175
2159
In the asynchronous case, ` Task.call_and_handle_blocking ` returns ` True ` if the
2176
- call to ` do_call ` blocks. If the ` callee ` blocks, ` on_start ` and ` on_return `
2177
- may be called after ` canon_lower ` has returned to the core wasm caller, which
2178
- is signaled via the ` subtask.state ` packed into the result ` i32 ` :
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:
2179
2169
``` python
2180
2170
def pack_async_result (i , state ):
2181
2171
assert (0 < i < 2 ** 30 )
@@ -2265,7 +2255,7 @@ async def canon_resource_drop(rt, sync, task, i):
2265
2255
else :
2266
2256
task.trap_if_on_the_stack(rt.impl)
2267
2257
else :
2268
- h.scope.drop_borrow()
2258
+ h.scope.need_to_drop -= 1
2269
2259
return flat_results
2270
2260
```
2271
2261
In general, the call to a resource's destructor is treated like a
@@ -2431,16 +2421,11 @@ validation specifies:
2431
2421
* ` $f ` is given type ` (func (param i32)) `
2432
2422
2433
2423
Calling ` $f ` removes the indicated subtask from the instance's table, trapping
2434
- if the subtask isn't done or isn't a subtask of the current task. The guard
2435
- on ` enqueued ` ensures that supertasks can only drop subtasks once they've been
2436
- officially notified of their completion (via ` task.wait ` or callback).
2424
+ if various conditions aren't met in ` Subtask.drop() ` .
2437
2425
``` python
2438
2426
async def canon_subtask_drop (task , i ):
2439
2427
trap_if(not task.inst.may_leave)
2440
- subtask = task.inst.async_subtasks.remove(i)
2441
- trap_if(subtask.enqueued)
2442
- trap_if(subtask.state != CallState.DONE )
2443
- subtask.task.num_async_subtasks -= 1
2428
+ task.inst.async_subtasks.remove(i).drop()
2444
2429
return []
2445
2430
```
2446
2431
0 commit comments