From f96a30439810e2782f11138e5f0a60f82fdfb00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Sat, 15 Jun 2024 00:52:02 +0300 Subject: [PATCH] Explicitly track async evaluation order of pending modules The execution order of modules that were waiting on a given async module was based on the order in which their [[AsyncEvaluation]] field was set to *true*. This PR replaces the [[AsyncEvaluation]] boolean with an actual number that keeps track of ordering in each agent. It also adds a note on when it's safe to reset this order to 0, since: - implementations might want to prevent this number from overflowing, and V8 currently has a bug that resets the number to 0 too early - figuring out when it's safe to reset the number might not be trivial, given the complexity of the module algorithms. I currently updated the example tables to just say that [[AsyncEvaluationOrder]] is set to "an integer" rather than *true*. In the next few days I'll update them to show the actual integer, but I first need to update https://nicolo-ribaudo.github.io/es-module-evaluation/ to verify that I'm not getting it wrong. --- spec.html | 111 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/spec.html b/spec.html index d182f4d537..16cde02c9f 100644 --- a/spec.html +++ b/spec.html @@ -12158,6 +12158,11 @@

Agents

a List of either Objects or Symbols Initially a new empty List, representing the list of objects and/or symbols to be kept alive until the end of the current Job + + [[AsyncEvaluationCount]] + an integer + Initially 0, used to track the order in which modules that are asynchronous or have asynchronous dependencies have their [[AsyncEvaluationOrder]] field set. + @@ -12199,6 +12204,21 @@

AgentCanSuspend ( ): a Boolean

In some environments it may not be reasonable for a given agent to suspend. For example, in a web browser environment, it may be reasonable to disallow suspending a document's main event handling thread, while still allowing workers' event handling threads to suspend.

+ + +

GetModuleAsyncEvaluationCount ( ): an integer

+
+
+ + 1. Let _AR_ be the Agent Record of the surrounding agent. + 1. Let _count_ be _AR_.[[AsyncEvaluationCount]]. + 1. Set _AR_.[[AsyncEvaluationCount]] to _count_ + 1. + 1. Return _count_. + + +

This value is only used to keep track of the relative evaluation order between pending modules. An implementation may unobservably reset [[AsyncEvaluationCount]] to 0 whenever there are no pending modules.

+
+
@@ -26387,13 +26407,13 @@

Cyclic Module Records

- [[AsyncEvaluation]] + [[AsyncEvaluationOrder]] - a Boolean + an integer or ~unset~ - Whether this module is either itself asynchronous or has an asynchronous dependency. Note: The order in which this field is set is used to order queued executions, see . + If this module is asynchronous or has an asynchronous dependency, this is a number representing the order in which modules that are asynchronous or have an asynchronous dependency start waiting on a pending promise. @@ -26730,7 +26750,7 @@

Evaluate ( ): a Promise

1. Else, 1. Assert: _module_.[[Status]] is either ~evaluating-async~ or ~evaluated~. 1. Assert: _module_.[[EvaluationError]] is ~empty~. - 1. If _module_.[[AsyncEvaluation]] is *false*, then + 1. If _module_.[[AsyncEvaluationOrder]] is ~unset~, then 1. Assert: _module_.[[Status]] is ~evaluated~. 1. Perform ! Call(_capability_.[[Resolve]], *undefined*, « *undefined* »). 1. Assert: _stack_ is empty. @@ -26780,13 +26800,12 @@

1. Set _requiredModule_ to _requiredModule_.[[CycleRoot]]. 1. Assert: _requiredModule_.[[Status]] is either ~evaluating-async~ or ~evaluated~. 1. If _requiredModule_.[[EvaluationError]] is not ~empty~, return ? _requiredModule_.[[EvaluationError]]. - 1. If _requiredModule_.[[AsyncEvaluation]] is *true*, then + 1. If _requiredModule_.[[AsyncEvaluationOrder]] is an integer, then 1. Set _module_.[[PendingAsyncDependencies]] to _module_.[[PendingAsyncDependencies]] + 1. 1. Append _module_ to _requiredModule_.[[AsyncParentModules]]. 1. If _module_.[[PendingAsyncDependencies]] > 0 or _module_.[[HasTLA]] is *true*, then - 1. Assert: _module_.[[AsyncEvaluation]] is *false* and was never previously set to *true*. - 1. Set _module_.[[AsyncEvaluation]] to *true*. - 1. NOTE: The order in which module records have their [[AsyncEvaluation]] fields transition to *true* is significant. (See .) + 1. Assert: _module_.[[AsyncEvaluationOrder]] is ~unset~ and was never previously set to an integer. + 1. Set _module_.[[AsyncEvaluationOrder]] to GetModuleAsyncEvaluationCount(). 1. If _module_.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(_module_). 1. Else, 1. Perform ? _module_.ExecuteModule(). @@ -26798,7 +26817,7 @@

1. Let _requiredModule_ be the last element of _stack_. 1. Remove the last element of _stack_. 1. Assert: _requiredModule_ is a Cyclic Module Record. - 1. If _requiredModule_.[[AsyncEvaluation]] is *false*, set _requiredModule_.[[Status]] to ~evaluated~. + 1. If _requiredModule_.[[AsyncEvaluationOrder]] is ~unset~, set _requiredModule_.[[Status]] to ~evaluated~. 1. Otherwise, set _requiredModule_.[[Status]] to ~evaluating-async~. 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. 1. Set _requiredModule_.[[CycleRoot]] to _module_. @@ -26853,7 +26872,7 @@

1. If _execList_ does not contain _m_ and _m_.[[CycleRoot]].[[EvaluationError]] is ~empty~, then 1. Assert: _m_.[[Status]] is ~evaluating-async~. 1. Assert: _m_.[[EvaluationError]] is ~empty~. - 1. Assert: _m_.[[AsyncEvaluation]] is *true*. + 1. Assert: _m_.[[AsyncEvaluationOrder]] is an integer. 1. Assert: _m_.[[PendingAsyncDependencies]] > 0. 1. Set _m_.[[PendingAsyncDependencies]] to _m_.[[PendingAsyncDependencies]] - 1. 1. If _m_.[[PendingAsyncDependencies]] = 0, then @@ -26879,17 +26898,17 @@

1. Assert: _module_.[[EvaluationError]] is not ~empty~. 1. Return ~unused~. 1. Assert: _module_.[[Status]] is ~evaluating-async~. - 1. Assert: _module_.[[AsyncEvaluation]] is *true*. + 1. Assert: _module_.[[AsyncEvaluationOrder]] is an integer. 1. Assert: _module_.[[EvaluationError]] is ~empty~. - 1. Set _module_.[[AsyncEvaluation]] to *false*. + 1. Set _module_.[[AsyncEvaluationOrder]] to ~unset~. 1. Set _module_.[[Status]] to ~evaluated~. 1. If _module_.[[TopLevelCapability]] is not ~empty~, then 1. Assert: _module_.[[CycleRoot]] and _module_ are the same Module Record. 1. Perform ! Call(_module_.[[TopLevelCapability]].[[Resolve]], *undefined*, « *undefined* »). 1. Let _execList_ be a new empty List. 1. Perform GatherAvailableAncestors(_module_, _execList_). - 1. Let _sortedExecList_ be a List whose elements are the elements of _execList_, in the order in which they had their [[AsyncEvaluation]] fields set to *true* in InnerModuleEvaluation. - 1. Assert: All elements of _sortedExecList_ have their [[AsyncEvaluation]] field set to *true*, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to ~empty~. + 1. Assert: All elements of _execList_ have their [[AsyncEvaluationOrder]] field set to an integer, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to ~empty~. + 1. Let _sortedExecList_ be a List whose elements are the elements of _execList_, sorted by ascending order of their [[AsyncEvaluationOrder]] field. 1. For each Cyclic Module Record _m_ of _sortedExecList_, do 1. If _m_.[[Status]] is ~evaluated~, then 1. Assert: _m_.[[EvaluationError]] is not ~empty~. @@ -26922,7 +26941,7 @@

1. Assert: _module_.[[EvaluationError]] is not ~empty~. 1. Return ~unused~. 1. Assert: _module_.[[Status]] is ~evaluating-async~. - 1. Assert: _module_.[[AsyncEvaluation]] is *true*. + 1. Assert: _module_.[[AsyncEvaluationOrder]] is an integer. 1. Assert: _module_.[[EvaluationError]] is ~empty~. 1. Set _module_.[[EvaluationError]] to ThrowCompletion(_error_). 1. Set _module_.[[Status]] to ~evaluated~. @@ -26990,7 +27009,7 @@

Example Cyclic Module Record Graphs

Loading and linking happen as before, and all modules end up with [[Status]] set to ~linked~.

-

Calling _A_.Evaluate() calls InnerModuleEvaluation on _A_, _B_, and _D_, which all transition to ~evaluating~. Then InnerModuleEvaluation is called on _A_ again, which is a no-op because it is already ~evaluating~. At this point, _D_.[[PendingAsyncDependencies]] is 0, so ExecuteAsyncModule(_D_) is called and we call _D_.ExecuteModule with a new PromiseCapability tracking the asynchronous execution of _D_. We unwind back to the InnerModuleEvaluation on _B_, setting _B_.[[PendingAsyncDependencies]] to 1 and _B_.[[AsyncEvaluation]] to *true*. We unwind back to the original InnerModuleEvaluation on _A_, setting _A_.[[PendingAsyncDependencies]] to 1. In the next iteration of the loop over _A_'s dependencies, we call InnerModuleEvaluation on _C_ and thus on _D_ (again a no-op) and _E_. As _E_ has no dependencies and is not part of a cycle, we call ExecuteAsyncModule(_E_) in the same manner as _D_ and _E_ is immediately removed from the stack. We unwind once more to the original InnerModuleEvaluation on _A_, setting _C_.[[AsyncEvaluation]] to *true*. Now we finish the loop over _A_'s dependencies, set _A_.[[AsyncEvaluation]] to *true*, and remove the entire strongly connected component from the stack, transitioning all of the modules to ~evaluating-async~ at once. At this point, the fields of the modules are as given in .

+

Calling _A_.Evaluate() calls InnerModuleEvaluation on _A_, _B_, and _D_, which all transition to ~evaluating~. Then InnerModuleEvaluation is called on _A_ again, which is a no-op because it is already ~evaluating~. At this point, _D_.[[PendingAsyncDependencies]] is 0, so ExecuteAsyncModule(_D_) is called and we call _D_.ExecuteModule with a new PromiseCapability tracking the asynchronous execution of _D_. We unwind back to the InnerModuleEvaluation on _B_, setting _B_.[[PendingAsyncDependencies]] to 1 and _B_.[[AsyncEvaluationOrder]]. We unwind back to the original InnerModuleEvaluation on _A_, setting _A_.[[PendingAsyncDependencies]] to 1. In the next iteration of the loop over _A_'s dependencies, we call InnerModuleEvaluation on _C_ and thus on _D_ (again a no-op) and _E_. As _E_ has no dependencies and is not part of a cycle, we call ExecuteAsyncModule(_E_) in the same manner as _D_ and _E_ is immediately removed from the stack. We unwind once more to the original InnerModuleEvaluation on _A_, setting _C_. Now we finish the loop over _A_'s dependencies, set _A_.[[AsyncEvaluationOrder]], and remove the entire strongly connected component from the stack, transitioning all of the modules to ~evaluating-async~ at once. At this point, the fields of the modules are as given in .

@@ -27000,7 +27019,7 @@

Example Cyclic Module Record Graphs

- + @@ -27010,7 +27029,7 @@

Example Cyclic Module Record Graphs

- + @@ -27019,7 +27038,7 @@

Example Cyclic Module Record Graphs

- + @@ -27028,7 +27047,7 @@

Example Cyclic Module Record Graphs

- + @@ -27037,7 +27056,7 @@

Example Cyclic Module Record Graphs

- + @@ -27046,7 +27065,7 @@

Example Cyclic Module Record Graphs

- + @@ -27063,7 +27082,7 @@

Example Cyclic Module Record Graphs

- + @@ -27073,7 +27092,7 @@

Example Cyclic Module Record Graphs

- + @@ -27082,7 +27101,7 @@

Example Cyclic Module Record Graphs

- + @@ -27099,7 +27118,7 @@

Example Cyclic Module Record Graphs

- + @@ -27109,7 +27128,7 @@

Example Cyclic Module Record Graphs

- + @@ -27118,7 +27137,7 @@

Example Cyclic Module Record Graphs

- + @@ -27127,7 +27146,7 @@

Example Cyclic Module Record Graphs

- + @@ -27144,7 +27163,7 @@

Example Cyclic Module Record Graphs

- + @@ -27154,7 +27173,7 @@

Example Cyclic Module Record Graphs

- + @@ -27163,7 +27182,7 @@

Example Cyclic Module Record Graphs

- + @@ -27180,7 +27199,7 @@

Example Cyclic Module Record Graphs

- + @@ -27190,7 +27209,7 @@

Example Cyclic Module Record Graphs

- + @@ -27199,7 +27218,7 @@

Example Cyclic Module Record Graphs

- + @@ -27216,7 +27235,7 @@

Example Cyclic Module Record Graphs

- + @@ -27226,7 +27245,7 @@

Example Cyclic Module Record Graphs

- + @@ -27243,7 +27262,7 @@

Example Cyclic Module Record Graphs

- + @@ -27254,7 +27273,7 @@

Example Cyclic Module Record Graphs

- + @@ -27264,7 +27283,7 @@

Example Cyclic Module Record Graphs

- + @@ -27282,7 +27301,7 @@

Example Cyclic Module Record Graphs

- + @@ -27293,7 +27312,7 @@

Example Cyclic Module Record Graphs

- + @@ -27311,7 +27330,7 @@

Example Cyclic Module Record Graphs

- + @@ -27322,7 +27341,7 @@

Example Cyclic Module Record Graphs

- + @@ -27332,7 +27351,7 @@

Example Cyclic Module Record Graphs

- + @@ -27882,7 +27901,7 @@

1. Else, 1. Append _ee_ to _indirectExportEntries_. 1. Let _async_ be _body_ Contains `await`. - 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: ~empty~, [[Namespace]]: ~empty~, [[CycleRoot]]: ~empty~, [[HasTLA]]: _async_, [[AsyncEvaluation]]: *false*, [[TopLevelCapability]]: ~empty~, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: ~empty~, [[Status]]: ~new~, [[EvaluationError]]: ~empty~, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[Context]]: ~empty~, [[ImportMeta]]: ~empty~, [[RequestedModules]]: _requestedModules_, [[LoadedModules]]: « », [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: ~empty~, [[DFSAncestorIndex]]: ~empty~ }. + 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: ~empty~, [[Namespace]]: ~empty~, [[CycleRoot]]: ~empty~, [[HasTLA]]: _async_, [[AsyncEvaluationOrder]]: ~unset~, [[TopLevelCapability]]: ~empty~, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: ~empty~, [[Status]]: ~new~, [[EvaluationError]]: ~empty~, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[Context]]: ~empty~, [[ImportMeta]]: ~empty~, [[RequestedModules]]: _requestedModules_, [[LoadedModules]]: « », [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: ~empty~, [[DFSAncestorIndex]]: ~empty~ }.

An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text.

[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
0 0 ~evaluating-async~*true*4 « » 2 (_B_ and _C_)
1 0 ~evaluating-async~*true*1 « _A_ » 1 (_D_)
3 0 ~evaluating-async~*true*3 « _A_ » 2 (_D_ and _E_)
2 0 ~evaluating-async~*true*0 « _B_, _C_ » 0
4 4 ~evaluating-async~*true*2 « _C_ » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
3 0 ~evaluating-async~*true*3 « _A_ » 1 (_D_)
4 4 ~evaluated~*true*2 « _C_ » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
1 0 ~evaluating-async~*true*1 « _A_ » 0
3 0 ~evaluating-async~*true*3 « _A_ » 0
2 0 ~evaluated~*true*0 « _B_, _C_ » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
0 0 ~evaluating-async~*true*4 « » 1 (_B_)
3 0 ~evaluated~*true*3 « _A_ » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
0 0 ~evaluating-async~*true*4 « » 0
1 0 ~evaluated~*true*1 « _A_ » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
0 0 ~evaluated~*true*4 « » 0
[[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]] 0 0 ~evaluated~*true*4 « » 1 (_B_) ~empty~ 3 0 ~evaluated~*true*3 « _A_ » 0 _C_'s evaluation error [[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]] 0 0 ~evaluated~*true*4 « » 0 _C_'s Evaluation Error [[DFSIndex]] [[DFSAncestorIndex]] [[Status]][[AsyncEvaluation]][[AsyncEvaluationOrder]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]] 0 0 ~evaluated~*true*4 « » 0 _C_'s Evaluation Error 1 0 ~evaluated~*true*1 « _A_ » 0 ~empty~