Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(50)

Unified Diff: pkg/front_end/lib/src/async_dependency_walker.dart

Issue 2552383002: Add an async version of dependency_walker.dart. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/front_end/lib/src/async_dependency_walker.dart
diff --git a/pkg/front_end/lib/src/async_dependency_walker.dart b/pkg/front_end/lib/src/async_dependency_walker.dart
new file mode 100644
index 0000000000000000000000000000000000000000..70120fdcfd59fe9cc6aae6ce521a7342dc4ec26d
--- /dev/null
+++ b/pkg/front_end/lib/src/async_dependency_walker.dart
@@ -0,0 +1,172 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/**
+ * An instance of [AsyncDependencyWalker] contains the core algorithms for
+ * walking a dependency graph and evaluating nodes in a safe order.
+ *
+ * Computation of dependencies and evaluation of nodes may be asynchronous.
+ */
+abstract class AsyncDependencyWalker<NodeType extends Node<NodeType>> {
+ /**
+ * Called by [walk] to evaluate a single non-cyclical node, after
+ * all that node's dependencies have been evaluated.
+ */
+ Future evaluate(NodeType v);
scheglov 2016/12/06 19:24:14 Would Future<Null> be better here and below as a r
Paul Berry 2016/12/06 20:28:45 Done.
+
+ /**
+ * Called by [walk] to evaluate a strongly connected component
+ * containing one or more nodes. All dependencies of the strongly
+ * connected component have been evaluated.
+ */
+ Future evaluateScc(List<NodeType> scc);
+
+ /**
+ * Walk the dependency graph starting at [startingPoint], finding
+ * strongly connected components and evaluating them in a safe order
+ * by calling [evaluate] and [evaluateScc].
+ *
+ * This is an implementation of Tarjan's strongly connected
+ * components algorithm
+ * (https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm).
+ *
+ * TODO(paulberry): Consider switching to an algorithm that allows
+ * dependencies to be computed in parallel, and nodes to be evaluated in
+ * parallel.
+ */
+ Future walk(NodeType startingPoint) async {
+ // TODO(paulberry): consider rewriting in a non-recursive way so
+ // that long dependency chains don't cause stack overflow.
+
+ // TODO(paulberry): in the event that an exception occurs during
+ // the walk, restore the state of the [Node] data structures so
+ // that further evaluation will be safe.
+
+ // The index which will be assigned to the next node that is
+ // freshly visited.
+ int index = 1;
+
+ // Stack of nodes which have been seen so far and whose strongly
+ // connected component is still being determined. Nodes are only
+ // popped off the stack when they are evaluated, so sometimes the
+ // stack contains nodes that were visited after the current node.
+ List<NodeType> stack = <NodeType>[];
+
+ Future strongConnect(NodeType node) async {
+ bool hasTrivialCycle = false;
+
+ // Assign the current node an index and add it to the stack. We
+ // haven't seen any of its dependencies yet, so set its lowLink
+ // to its index, indicating that so far it is the only node in
+ // its strongly connected component.
+ node._index = node._lowLink = index++;
+ stack.add(node);
+
+ // Consider the node's dependencies one at a time.
+ var dependencies =
+ node._dependencies ??= await node.computeDependencies();
+ for (NodeType dependency in dependencies) {
+ // If the dependency has already been evaluated, it can't be
+ // part of this node's strongly connected component, so we can
+ // skip it.
+ if (dependency._isEvaluated) {
+ continue;
+ }
+ if (identical(node, dependency)) {
+ // If a node includes itself as a dependency, there is no need to
+ // explore the dependency further.
+ hasTrivialCycle = true;
+ } else if (dependency._index == 0) {
+ // The dependency hasn't been seen yet, so recurse on it.
+ await strongConnect(dependency);
+ // If the dependency's lowLink refers to a node that was
+ // visited before the current node, that means that the
+ // current node, the dependency, and the node referred to by
+ // the dependency's lowLink are all part of the same
+ // strongly connected component, so we need to update the
+ // current node's lowLink accordingly.
+ if (dependency._lowLink < node._lowLink) {
+ node._lowLink = dependency._lowLink;
+ }
+ } else {
+ // The dependency has already been seen, so it is part of
+ // the current node's strongly connected component. If it
+ // was visited earlier than the current node's lowLink, then
+ // it is a new addition to the current node's strongly
+ // connected component, so we need to update the current
+ // node's lowLink accordingly.
+ if (dependency._index < node._lowLink) {
+ node._lowLink = dependency._index;
+ }
+ }
+ }
+
+ // If the current node's lowLink is the same as its index, then
+ // we have finished visiting a strongly connected component, so
+ // pop the stack and evaluate it before moving on.
+ if (node._lowLink == node._index) {
+ // The strongly connected component has only one node. If there is a
+ // cycle, it's a trivial one.
+ if (identical(stack.last, node)) {
+ stack.removeLast();
+ node._isEvaluated = true;
+ if (hasTrivialCycle) {
+ await evaluateScc(<NodeType>[node]);
+ } else {
+ await evaluate(node);
+ }
+ } else {
+ // There are multiple nodes in the strongly connected
+ // component.
+ List<NodeType> scc = <NodeType>[];
+ while (true) {
+ NodeType otherNode = stack.removeLast();
+ scc.add(otherNode);
+ otherNode._isEvaluated = true;
+ if (identical(otherNode, node)) {
+ break;
+ }
+ }
+ await evaluateScc(scc);
+ }
+ }
+ }
+
+ // Kick off the algorithm starting with the starting point.
+ await strongConnect(startingPoint);
+ }
+}
+
+/**
+ * Instances of [Node] represent nodes in a dependency graph. The
+ * type parameter, [NodeType], is the derived type (this affords some
+ * extra type safety by making it difficult to accidentally construct
+ * bridges between unrelated dependency graphs).
+ */
+abstract class Node<NodeType> {
+ /**
+ * Index used by Tarjan's strongly connected components algorithm.
+ * Zero means the node has not been visited yet; a nonzero value
+ * counts the order in which the node was visited.
+ */
+ int _index = 0;
+
+ /**
+ * Low link used by Tarjan's strongly connected components
+ * algorithm. This represents the smallest [_index] of all the nodes
+ * in the strongly connected component to which this node belongs.
+ */
+ int _lowLink = 0;
+
+ List<NodeType> _dependencies;
+
+ bool _isEvaluated = false;
+
+ /**
+ * Compute the dependencies of this node.
+ */
+ Future<List<NodeType>> computeDependencies();
+}

Powered by Google App Engine
This is Rietveld 408576698