@@ -636,15 +636,9 @@ above and allows a pending task to start.
636636
637637While ` canon_lift ` creates ` Task ` s, ` canon_lower ` creates ` Subtask ` objects.
638638Like ` 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.
648642``` python
649643class Subtask (Context ):
650644 ft: FuncType
@@ -664,7 +658,57 @@ class Subtask(Context):
664658 self .lenders = []
665659 self .notify_supertask = False
666660 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+ ```
667704
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
668712 def on_start (self ):
669713 assert (self .state == CallState.STARTING )
670714 self .state = CallState.STARTED
@@ -681,42 +725,20 @@ class Subtask(Context):
681725 ts = self .ft.result_types()
682726 self .flat_results = lower_flat_values(self , max_flat, vs, ts, self .flat_args)
683727```
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)
713728
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
714735 def finish (self ):
715736 assert (self .state == CallState.RETURNED )
716- for h in self .lenders:
717- h.lend_count -= 1
718737 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()
720742 return self .flat_results
721743```
722744
@@ -1141,12 +1163,12 @@ def lift_borrow(cx, i, t):
11411163 assert (isinstance (cx, Subtask))
11421164 h = cx.inst.handles.get(t.rt, i)
11431165 if h.own:
1144- cx.track_owning_lend (h)
1166+ cx.add_lender (h)
11451167 return h.rep
11461168```
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
11501172` borrow ` handle can be held). This tracking is only required when ` h ` is an
11511173` own ` handle because, when ` h ` is a ` borrow ` handle, this tracking has already
11521174happened (when the originating ` own ` handle was lifted) for a strictly longer
0 commit comments