@@ -636,15 +636,9 @@ above and allows a pending task to start.
636
636
637
637
While ` canon_lift ` creates ` Task ` s, ` canon_lower ` creates ` Subtask ` objects.
638
638
Like ` Task ` , ` Subtask ` is a subclass of ` Context ` and stores the ` ft ` and
639
- ` opts ` of its ` canon lower ` . Importantly, the ` Context.task ` field of a
640
- ` Subtask ` refers to the [ current task] wwhich called ` canon lower ` , thereby
641
- linking all subtasks to their supertask, maintaining a (possibly asynchronous)
642
- call tree. The ` on_start ` and ` on_return ` methods of ` Subtask ` are passed (by
643
- ` canon_lower ` below) to the callee, which will call them to lift its arguments
644
- and lower its results. Using callbacks provides the callee the flexibility to
645
- control when arguments are lowered (which can vary due to backpressure) and
646
- when results are lifted (which can also vary due to when ` task.return ` is
647
- called).
639
+ ` opts ` of its ` canon lower ` . Importantly, the ` task ` field of a ` Subtask `
640
+ refers to the [ current task] which called ` canon lower ` , thereby linking all
641
+ subtasks to their supertask, maintaining the (possibly asynchronous) call tree.
648
642
``` python
649
643
class Subtask (Context ):
650
644
ft: FuncType
@@ -664,7 +658,57 @@ class Subtask(Context):
664
658
self .lenders = []
665
659
self .notify_supertask = False
666
660
self .enqueued = False
661
+ ```
662
+
663
+ The ` lenders ` field of ` Subtask ` maintains a list of all the owned handles
664
+ that have been lent to a subtask and must therefor not be dropped until the
665
+ subtask completes. The ` add_lender ` method is called (below) when lifting an
666
+ owned handle and increments the ` lend_count ` of the owned handle, which is
667
+ guarded to be zero by ` canon_resource_drop ` (below). The ` release_lenders `
668
+ method releases all the ` lend_count ` s of all such handles lifted for the
669
+ subtask and is called (below) when the subtask finishes.
670
+ ``` python
671
+ def add_lender (self , lending_handle ):
672
+ assert (lending_handle.own)
673
+ lending_handle.lend_count += 1
674
+ self .lenders.append(lending_handle)
675
+
676
+ def release_lenders (self ):
677
+ for h in self .lenders:
678
+ h.lend_count -= 1
679
+ ```
680
+ Note, the ` lenders ` list usually has a fixed size (in all cases except when a
681
+ function signature has ` borrow ` s in ` list ` s or ` stream ` s) and thus can be
682
+ stored inline in the native stack frame.
683
+
684
+ The ` maybe_notify_supertask ` method called by ` on_start ` , ` on_return ` and
685
+ ` finish ` (next) only sends events to the supertask if this ` Subtask ` actually
686
+ blocked and got added to the ` async_subtasks ` table (signalled by
687
+ ` notify_supertask ` being set). Additionally, ` maybe_notify_supertask ` uses the
688
+ ` enqueued ` flag and the fact that "events" are first-class functions to
689
+ collapse N events down to 1 if a subtask advances state multiple times before
690
+ the supertask receives the event which, in turn, avoids unnecessarily spamming
691
+ the event loop when only the most recent state matters.
692
+ ``` python
693
+ def maybe_notify_supertask (self ):
694
+ if self .notify_supertask and not self .enqueued:
695
+ self .enqueued = True
696
+ def subtask_event ():
697
+ self .enqueued = False
698
+ i = self .inst.async_subtasks.array.index(self )
699
+ if self .state == CallState.DONE :
700
+ self .release_lenders()
701
+ return (EventCode(self .state), i)
702
+ self .task.notify(subtask_event)
703
+ ```
667
704
705
+ The ` on_start ` and ` on_return ` methods of ` Subtask ` are passed (by
706
+ ` canon_lower ` below) to the callee to be called to lift its arguments and
707
+ lower its results. Using callbacks provides the callee the flexibility to
708
+ control when arguments are lowered (which can vary due to backpressure) and
709
+ when results are lifted (which can also vary due to when ` task.return ` is
710
+ called).
711
+ ``` python
668
712
def on_start (self ):
669
713
assert (self .state == CallState.STARTING )
670
714
self .state = CallState.STARTED
@@ -681,42 +725,20 @@ class Subtask(Context):
681
725
ts = self .ft.result_types()
682
726
self .flat_results = lower_flat_values(self , max_flat, vs, ts, self .flat_args)
683
727
```
684
- The ` maybe_notify_supertask ` method called by ` on_start ` and ` on_return ` only
685
- sends events to the supertask if this ` Subtask ` actually blocked and got added
686
- to the ` async_subtasks ` table (signalled by ` notify_supertask ` being set).
687
- Additionally, ` maybe_notify_supertask ` uses the ` enqueued ` flag and the fact
688
- that "events" are first-class functions to collapse N events down to 1 if a
689
- subtask advances state multiple times before the supertask receives the event
690
- which, in turn, avoids unnecessarily spamming the event loop when only the most
691
- recent state matters.
692
- ``` python
693
- def maybe_notify_supertask (self ):
694
- if self .notify_supertask and not self .enqueued:
695
- self .enqueued = True
696
- def subtask_event ():
697
- self .enqueued = False
698
- i = self .inst.async_subtasks.array.index(self )
699
- return (EventCode(self .state), i)
700
- self .task.notify(subtask_event)
701
- ```
702
- Lastly, a ` Subtask ` tracks the owned handles that have been lent for the
703
- duration of the call, ensuring that the caller doesn't drop them during the
704
- call (which might create a dangling borrowed handle in the callee). Note, the
705
- ` lenders ` list usually has a fixed size (in all cases except when a function
706
- signature has ` borrow ` s in ` list ` s) and thus can be stored inline in the native
707
- stack frame.
708
- ``` python
709
- def track_owning_lend (self , lending_handle ):
710
- assert (lending_handle.own)
711
- lending_handle.lend_count += 1
712
- self .lenders.append(lending_handle)
713
728
729
+ Lastly, when a ` Subtask ` finishes, it calls ` release_lenders ` to allow owned
730
+ handles passed to this subtask to be dropped. In the synchronous or eager case
731
+ this happens immediately before returning to the caller. In the
732
+ asynchronous+blocking case, this happens right before the ` CallState.DONE `
733
+ event is delivered to the guest program.
734
+ ``` python
714
735
def finish (self ):
715
736
assert (self .state == CallState.RETURNED )
716
- for h in self .lenders:
717
- h.lend_count -= 1
718
737
self .state = CallState.DONE
719
- self .maybe_notify_supertask()
738
+ if self .opts.sync or not self .notify_supertask:
739
+ self .release_lenders()
740
+ else :
741
+ self .maybe_notify_supertask()
720
742
return self .flat_results
721
743
```
722
744
@@ -1141,12 +1163,12 @@ def lift_borrow(cx, i, t):
1141
1163
assert (isinstance (cx, Subtask))
1142
1164
h = cx.inst.handles.get(t.rt, i)
1143
1165
if h.own:
1144
- cx.track_owning_lend (h)
1166
+ cx.add_lender (h)
1145
1167
return h.rep
1146
1168
```
1147
- The ` track_owning_lend ` call to ` Context ` participates in the enforcement
1148
- of the dynamic borrow rules, which keep the source ` own ` handle alive until the
1149
- end of the call (as an intentionally-conservative upper bound on how long the
1169
+ The ` add_lender ` call to ` Context ` participates in the enforcement of the
1170
+ dynamic borrow rules, which keep the source ` own ` handle alive until the end
1171
+ of the call (as an intentionally-conservative upper bound on how long the
1150
1172
` borrow ` handle can be held). This tracking is only required when ` h ` is an
1151
1173
` own ` handle because, when ` h ` is a ` borrow ` handle, this tracking has already
1152
1174
happened (when the originating ` own ` handle was lifted) for a strictly longer
0 commit comments