Repository: PrefectHQ/prefect

Issue:
Scheduled start time not updating between runs
_Originally posted by @roveo in https://github.com/PrefectHQ/prefect/pull/1418#issuecomment-526562411_


I think there's something wrong with this. Here's a very simple flow logging it's context:

```py
import prefect
import time
from datetime import timedelta
from pprint import pprint


class Task1(prefect.Task):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def run(self):
        run = prefect.context["scheduled_start_time"].strftime("%Y-%m-%d %H:%M:%S")
        self.logger.warning(pprint(prefect.context.__dict__))
        return 5


@prefect.task
def task2(x):
    pass


schedule = prefect.schedules.CronSchedule("* * * * *")
flow = prefect.Flow("test_flow_name", schedule=schedule)

with flow:
    task2(Task1())


if __name__ == "__main__":
    flow.run()
```

And here's the output of the first two runs. `scheduled_start_time` doesn't change between runs.

```
 ❯ python test_scheduled_start_time.py
[2019-08-30 11:07:17,364] INFO - prefect.Flow: test_flow_name | Waiting for next scheduled run at 2019-08-30T11:08:00+00:00
[2019-08-30 11:08:00,001] INFO - prefect.FlowRunner | Beginning Flow run for 'test_flow_name'
[2019-08-30 11:08:00,002] INFO - prefect.FlowRunner | Starting flow run.
[2019-08-30 11:08:00,014] INFO - prefect.TaskRunner | Task 'Task1': Starting task run...
{'caches': {},
 'checkpointing': False,
 'date': DateTime(2019, 8, 30, 11, 8, 0, 2173, tzinfo=Timezone('UTC')),
 'flow_name': 'test_flow_name',
 'logger': <Logger prefect.Task: Task1 (INFO)>,
 'map_index': None,
 'scheduled_start_time': DateTime(2019, 8, 30, 11, 8, 0, tzinfo=Timezone('UTC')),
 'task_full_name': 'Task1',
 'task_name': 'Task1',
 'task_run_count': 1,
 'task_slug': 'a6bf86d3-2c63-4ad7-b591-0a9c9bd93eeb',
 'task_tags': set(),
 'today': '2019-08-30',
 'today_nodash': '20190830',
 'tomorrow': '2019-08-31',
 'tomorrow_nodash': '20190831',
 'yesterday': '2019-08-29',
 'yesterday_nodash': '20190829'}
[2019-08-30 11:08:00,015] WARNING - prefect.Task: Task1 | None
[2019-08-30 11:08:00,016] INFO - prefect.TaskRunner | Task 'Task1': finished task run for task with final state: 'Success'
[2019-08-30 11:08:00,017] INFO - prefect.TaskRunner | Task 'task2': Starting task run...
[2019-08-30 11:08:00,017] INFO - prefect.TaskRunner | Task 'task2': finished task run for task with final state: 'Success'
[2019-08-30 11:08:00,020] INFO - prefect.FlowRunner | Flow run SUCCESS: all reference tasks succeeded
[2019-08-30 11:08:00,028] INFO - prefect.Flow: test_flow_name | Waiting for next scheduled run at 2019-08-30T11:09:00+00:00
[2019-08-30 11:09:00,003] INFO - prefect.FlowRunner | Beginning Flow run for 'test_flow_name'
[2019-08-30 11:09:00,003] INFO - prefect.FlowRunner | Starting flow run.
[2019-08-30 11:09:00,015] INFO - prefect.TaskRunner | Task 'Task1': Starting task run...
{'caches': {'Task1': [], 'task2': []},
 'checkpointing': False,
 'date': DateTime(2019, 8, 30, 11, 9, 0, 3252, tzinfo=Timezone('UTC')),
 'flow_name': 'test_flow_name',
 'logger': <Logger prefect.Task: Task1 (INFO)>,
 'map_index': None,
 'scheduled_start_time': DateTime(2019, 8, 30, 11, 8, 0, tzinfo=Timezone('UTC')),
 'task_full_name': 'Task1',
 'task_name': 'Task1',
 'task_run_count': 1,
 'task_slug': 'a6bf86d3-2c63-4ad7-b591-0a9c9bd93eeb',
 'task_tags': set(),
 'today': '2019-08-30',
 'today_nodash': '20190830',
 'tomorrow': '2019-08-31',
 'tomorrow_nodash': '20190831',
 'yesterday': '2019-08-29',
 'yesterday_nodash': '20190829'}
[2019-08-30 11:09:00,023] WARNING - prefect.Task: Task1 | None
[2019-08-30 11:09:00,024] INFO - prefect.TaskRunner | Task 'Task1': finished task run for task with final state: 'Success'
[2019-08-30 11:09:00,024] INFO - prefect.TaskRunner | Task 'task2': Starting task run...
[2019-08-30 11:09:00,025] INFO - prefect.TaskRunner | Task 'task2': finished task run for task with final state: 'Success'
[2019-08-30 11:09:00,025] INFO - prefect.FlowRunner | Flow run SUCCESS: all reference tasks succeeded
```




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
@@ -843,10 +843,12 @@ def _run_on_schedule(
         flow_run_context = kwargs.pop(
             "context", {}
         ).copy()  # copy to avoid modification
-        flow_run_context.setdefault("scheduled_start_time", next_run_time)
 
         ## run this flow indefinitely, so long as its schedule has future dates
         while True:
+
+            flow_run_context.update(scheduled_start_time=next_run_time)
+
             if flow_state.is_scheduled():
                 next_run_time = flow_state.start_time
                 now = pendulum.now("utc")
@@ -1001,7 +1003,7 @@ def run(
             run_on_schedule = cast(bool, prefect.config.flows.run_on_schedule)
         if run_on_schedule is False:
             runner = runner_cls(flow=self)
-            state = runner.run(parameters=parameters, **kwargs)
+            state = runner.run(parameters=parameters, return_tasks=self.tasks, **kwargs)
         else:
             state = self._run_on_schedule(
                 parameters=parameters, runner_cls=runner_cls, **kwargs


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
@@ -1514,6 +1514,18 @@ def run(self):
         state = f.run(run_on_schedule=False)
         assert t.call_count == 1
 
+    def test_flow_dot_run_returns_tasks_when_running_off_schedule(self):
+        @prefect.task
+        def test_task():
+            return 2
+
+        f = Flow(name="test", tasks=[test_task])
+        res = f.run(run_on_schedule=False)
+
+        assert res.is_successful()
+        assert res.result[test_task].is_successful()
+        assert res.result[test_task].result == 2
+
     def test_flow_dot_run_responds_to_config(self):
         class MockSchedule(prefect.schedules.Schedule):
             call_count = 0
@@ -2060,6 +2072,25 @@ def report_start_time():
         state = f.run()
         assert state.result[report_start_time].result is start_time
 
+    def test_flow_dot_run_updates_the_scheduled_start_time_of_each_scheduled_run(self):
+
+        start_times = [pendulum.now().add(seconds=i * 0.1) for i in range(1, 4)]
+        REPORTED_START_TIMES = []
+
+        @task
+        def record_start_time():
+            REPORTED_START_TIMES.append(prefect.context.scheduled_start_time)
+
+        f = Flow(
+            name="test",
+            tasks=[record_start_time],
+            schedule=prefect.schedules.Schedule(
+                clocks=[prefect.schedules.clocks.DatesClock(dates=start_times)]
+            ),
+        )
+        f.run()
+        assert REPORTED_START_TIMES == start_times
+
 
 class TestFlowDeploy:
     @pytest.mark.parametrize(
diff --git a/tests/engine/test_flow_runner.py b/tests/engine/test_flow_runner.py
--- a/tests/engine/test_flow_runner.py
+++ b/tests/engine/test_flow_runner.py
@@ -1380,18 +1380,33 @@ def return_scheduled_start_time():
             res.result[return_scheduled_start_time].result, datetime.datetime
         )
 
-    def test_flow_runner_doesnt_override_scheduled_start_time(self):
+    def test_flow_runner_does_override_scheduled_start_time_when_running_off_schedule(
+        self
+    ):
         @prefect.task
         def return_scheduled_start_time():
             return prefect.context.get("scheduled_start_time")
 
         f = Flow(name="test", tasks=[return_scheduled_start_time])
-        res = f.run(context=dict(scheduled_start_time=42))
+        res = f.run(context=dict(scheduled_start_time=42), run_on_schedule=False)
 
         assert res.is_successful()
         assert res.result[return_scheduled_start_time].is_successful()
         assert res.result[return_scheduled_start_time].result == 42
 
+    def test_flow_runner_doesnt_override_scheduled_start_time_when_running_on_schedule(
+        self
+    ):
+        @prefect.task
+        def return_scheduled_start_time():
+            return prefect.context.get("scheduled_start_time")
+
+        f = Flow(name="test", tasks=[return_scheduled_start_time])
+        res = f.run(context=dict(scheduled_start_time=42), run_on_schedule=True)
+
+        assert res.is_successful()
+        assert res.result[return_scheduled_start_time].result != 42
+
     @pytest.mark.parametrize(
         "date", ["today_nodash", "tomorrow_nodash", "yesterday_nodash"]
     )