Repository: PrefectHQ/prefect

Issue:
`start_tasks` kwarg doesn't work
The `start_tasks` kwarg appears to not be working:

```python
from prefect import *

GLOBAL_DICT = {'key': 0}

class PrintTask(Task):
    def run(self):
        GLOBAL_DICT['key'] += 1
        return 1

class ReturnTask(Task):
    was_called = False
    def run(self, x):
        if self.was_called is False:
            self.was_called = True
            raise ValueError("error")
        return x

with Flow() as f:
    t1 = PrintTask()
    t2 = ReturnTask(max_retries=1)
    result = t2(t1())

state = f.run(return_tasks=[t2])
state = f.run(return_tasks=[t2], start_tasks=[t2])

## GLOBAL_DICT['key'] == 2
```


Discussion:

Resolving patch:
diff --git a/src/prefect/core/flow.py b/src/prefect/core/flow.py
--- a/src/prefect/core/flow.py
+++ b/src/prefect/core/flow.py
@@ -30,21 +30,12 @@
 import prefect.schedules
 from prefect.core.edge import Edge
 from prefect.core.task import Parameter, Task
-from prefect.utilities.functions import cache
 from prefect.utilities.json import Serializable
 from prefect.utilities.tasks import as_task
 
 ParameterDetails = TypedDict("ParameterDetails", {"default": Any, "required": bool})
 
 
-def flow_cache_key(flow: "Flow") -> int:
-    """
-    Returns a cache key that can be used to determine if the cache is stale.
-    """
-
-    return hash((frozenset(flow.tasks), frozenset(flow.edges)))
-
-
 class Flow(Serializable):
     def __init__(
         self,
@@ -77,7 +68,6 @@ def __init__(
             )
 
         self._prefect_version = prefect.__version__
-        self._cache = {}
 
         super().__init__()
 
@@ -122,7 +112,6 @@ def __exit__(self, _type, _value, _tb) -> None:  # type: ignore
 
     # Introspection ------------------------------------------------------------
 
-    @cache(validation_fn=flow_cache_key)
     def root_tasks(self) -> Set[Task]:
         """
         Returns the root tasks of the Flow -- tasks that have no upstream
@@ -130,7 +119,6 @@ def root_tasks(self) -> Set[Task]:
         """
         return set(t for t in self.tasks if not self.edges_to(t))
 
-    @cache(validation_fn=flow_cache_key)
     def terminal_tasks(self) -> Set[Task]:
         """
         Returns the terminal tasks of the Flow -- tasks that have no downstream
@@ -247,14 +235,12 @@ def update(self, flow: "Flow", validate: bool = True) -> None:
                         validate=False,
                     )
 
-    @cache(validation_fn=flow_cache_key)
     def all_upstream_edges(self) -> Dict[Task, Set[Edge]]:
         edges = {t: set() for t in self.tasks}  # type: Dict[Task, Set[Edge]]
         for edge in self.edges:
             edges[edge.downstream_task].add(edge)
         return edges
 
-    @cache(validation_fn=flow_cache_key)
     def all_downstream_edges(self) -> Dict[Task, Set[Edge]]:
         edges = {t: set() for t in self.tasks}  # type: Dict[Task, Set[Edge]]
         for edge in self.edges:
@@ -287,7 +273,6 @@ def validate(self) -> None:
         """
         self.sorted_tasks()
 
-    @cache(validation_fn=flow_cache_key)
     def sorted_tasks(self, root_tasks: Iterable[Task] = None) -> Tuple[Task, ...]:
 
         # begin by getting all tasks under consideration (root tasks and all
diff --git a/src/prefect/utilities/__init__.py b/src/prefect/utilities/__init__.py
--- a/src/prefect/utilities/__init__.py
+++ b/src/prefect/utilities/__init__.py
@@ -2,7 +2,6 @@
 import prefect.utilities.datetimes
 import prefect.utilities.exceptions
 import prefect.utilities.flows
-import prefect.utilities.functions
 import prefect.utilities.json
 import prefect.utilities.strings
 import prefect.utilities.tasks
diff --git a/src/prefect/utilities/functions.py b/src/prefect/utilities/functions.py
deleted file mode 100644
--- a/src/prefect/utilities/functions.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Any, Callable, Hashable, TypeVar
-
-import wrapt
-
-T = TypeVar("T")
-
-
-def cache(validation_fn: Callable[[Any], Hashable]) -> Callable:
-    @wrapt.decorator
-    def inner(function: Callable[..., T], instance, args, kwargs) -> T:
-        validation_key = validation_fn(instance)
-        function_key = (function.__qualname__, args, frozenset(kwargs))
-
-        if instance._cache.get("validation key", None) != validation_key:
-            instance._cache.clear()
-            instance._cache["validation key"] = validation_key
-
-        if function_key in instance._cache:
-            return instance._cache[function_key]
-
-        else:
-            result = function(*args, **kwargs)
-            instance._cache[function_key] = result
-            return result
-
-    return inner


Test patch:
diff --git a/tests/core/test_flow.py b/tests/core/test_flow.py
--- a/tests/core/test_flow.py
+++ b/tests/core/test_flow.py
@@ -267,3 +267,68 @@ def test_upstream_and_downstream_error_msgs_when_task_is_not_in_flow():
     with pytest.raises(ValueError) as e:
         f.downstream_tasks(t)
         assert "was not found in Flow" in e
+
+
+def test_sorted_tasks():
+    """
+    t1 -> t2 -> t3 -> t4
+    """
+    f = Flow()
+    t1 = Task("1")
+    t2 = Task("2")
+    t3 = Task("3")
+    t4 = Task("4")
+    f.add_edge(t1, t2)
+    f.add_edge(t2, t3)
+    f.add_edge(t3, t4)
+    assert f.sorted_tasks() == (t1, t2, t3, t4)
+
+
+def test_sorted_tasks_with_ambiguous_sort():
+    """
+    t1 -> bottleneck
+    t2 -> bottleneck
+    t3 -> bottleneck
+           bottleneck -> t4
+           bottleneck -> t5
+           bottleneck -> t6
+    """
+
+    f = Flow()
+    t1 = Task("1")
+    t2 = Task("2")
+    t3 = Task("3")
+    t4 = Task("4")
+    t5 = Task("5")
+    t6 = Task("6")
+    bottleneck = Task("bottleneck")
+    f.add_edge(t1, bottleneck)
+    f.add_edge(t2, bottleneck)
+    f.add_edge(t3, bottleneck)
+    f.add_edge(bottleneck, t4)
+    f.add_edge(bottleneck, t5)
+    f.add_edge(bottleneck, t6)
+
+    tasks = f.sorted_tasks()
+    assert set(tasks[:3]) == set([t1, t2, t3])
+    assert list(tasks)[3] is bottleneck
+    assert set(tasks[4:]) == set([t4, t5, t6])
+
+
+def test_sorted_tasks_with_start_task():
+    """
+    t1 -> t2 -> t3 -> t4
+                  t3 -> t5
+    """
+    f = Flow()
+    t1 = Task("1")
+    t2 = Task("2")
+    t3 = Task("3")
+    t4 = Task("4")
+    t5 = Task("5")
+    f.add_edge(t1, t2)
+    f.add_edge(t2, t3)
+    f.add_edge(t3, t4)
+    f.add_edge(t3, t5)
+    assert set(f.sorted_tasks(root_tasks=[])) == set([t1, t2, t3, t4, t5])
+    assert set(f.sorted_tasks(root_tasks=[t3])) == set([t3, t4, t5])