Skip to content

Commit 9f20885

Browse files
authored
Fully inplement TaskRunner for UWP (flutter#70890) (flutter#28013)
1 parent f555769 commit 9f20885

File tree

10 files changed

+309
-202
lines changed

10 files changed

+309
-202
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,9 @@ FILE: ../../../flutter/shell/platform/windows/system_utils.h
17541754
FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc
17551755
FILE: ../../../flutter/shell/platform/windows/system_utils_win32.cc
17561756
FILE: ../../../flutter/shell/platform/windows/system_utils_winuwp.cc
1757+
FILE: ../../../flutter/shell/platform/windows/task_runner.cc
17571758
FILE: ../../../flutter/shell/platform/windows/task_runner.h
1759+
FILE: ../../../flutter/shell/platform/windows/task_runner_unittests.cc
17581760
FILE: ../../../flutter/shell/platform/windows/task_runner_win32.cc
17591761
FILE: ../../../flutter/shell/platform/windows/task_runner_win32.h
17601762
FILE: ../../../flutter/shell/platform/windows/task_runner_win32_window.cc

shell/platform/windows/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ source_set("flutter_windows_source") {
7777
"sequential_id_generator.cc",
7878
"sequential_id_generator.h",
7979
"system_utils.h",
80+
"task_runner.cc",
8081
"task_runner.h",
8182
"text_input_plugin.cc",
8283
"text_input_plugin.h",
@@ -226,6 +227,7 @@ executable("flutter_windows_unittests") {
226227
"sequential_id_generator_unittests.cc",
227228
"string_conversion_unittests.cc",
228229
"system_utils_unittests.cc",
230+
"task_runner_unittests.cc",
229231
"testing/engine_modifier.h",
230232
"testing/mock_gl_functions.h",
231233
]

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project)
143143
FlutterEngineGetProcAddresses(&embedder_api_);
144144

145145
task_runner_ = TaskRunner::Create(
146-
GetCurrentThreadId(), embedder_api_.GetCurrentTime,
147-
[this](const auto* task) {
146+
embedder_api_.GetCurrentTime, [this](const auto* task) {
148147
if (!engine_) {
149148
std::cerr << "Cannot post an engine task when engine is not running."
150149
<< std::endl;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/windows/task_runner.h"
6+
7+
#include <atomic>
8+
#include <utility>
9+
10+
namespace flutter {
11+
12+
TaskRunner::TaskRunner(CurrentTimeProc get_current_time,
13+
const TaskExpiredCallback& on_task_expired)
14+
: get_current_time_(get_current_time),
15+
on_task_expired_(std::move(on_task_expired)) {}
16+
17+
std::chrono::nanoseconds TaskRunner::ProcessTasks() {
18+
const TaskTimePoint now = TaskTimePoint::clock::now();
19+
20+
std::vector<Task> expired_tasks;
21+
22+
// Process expired tasks.
23+
{
24+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
25+
while (!task_queue_.empty()) {
26+
const auto& top = task_queue_.top();
27+
// If this task (and all tasks after this) has not yet expired, there is
28+
// nothing more to do. Quit iterating.
29+
if (top.fire_time > now) {
30+
break;
31+
}
32+
33+
// Make a record of the expired task. Do NOT service the task here
34+
// because we are still holding onto the task queue mutex. We don't want
35+
// other threads to block on posting tasks onto this thread till we are
36+
// done processing expired tasks.
37+
expired_tasks.push_back(task_queue_.top());
38+
39+
// Remove the tasks from the delayed tasks queue.
40+
task_queue_.pop();
41+
}
42+
}
43+
44+
// Fire expired tasks.
45+
{
46+
// Flushing tasks here without holing onto the task queue mutex.
47+
for (const auto& task : expired_tasks) {
48+
if (auto flutter_task = std::get_if<FlutterTask>(&task.variant)) {
49+
on_task_expired_(flutter_task);
50+
} else if (auto closure = std::get_if<TaskClosure>(&task.variant))
51+
(*closure)();
52+
}
53+
}
54+
55+
// Calculate duration to sleep for on next iteration.
56+
{
57+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
58+
const auto next_wake = task_queue_.empty() ? TaskTimePoint::max()
59+
: task_queue_.top().fire_time;
60+
61+
return std::min(next_wake - now, std::chrono::nanoseconds::max());
62+
}
63+
}
64+
65+
TaskRunner::TaskTimePoint TaskRunner::TimePointFromFlutterTime(
66+
uint64_t flutter_target_time_nanos) const {
67+
const auto now = TaskTimePoint::clock::now();
68+
const auto flutter_duration = flutter_target_time_nanos - get_current_time_();
69+
return now + std::chrono::nanoseconds(flutter_duration);
70+
}
71+
72+
void TaskRunner::PostFlutterTask(FlutterTask flutter_task,
73+
uint64_t flutter_target_time_nanos) {
74+
Task task;
75+
task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos);
76+
task.variant = flutter_task;
77+
EnqueueTask(std::move(task));
78+
}
79+
80+
void TaskRunner::PostTask(TaskClosure closure) {
81+
Task task;
82+
task.fire_time = TaskTimePoint::clock::now();
83+
task.variant = std::move(closure);
84+
EnqueueTask(std::move(task));
85+
}
86+
87+
void TaskRunner::EnqueueTask(Task task) {
88+
static std::atomic_uint64_t sGlobalTaskOrder(0);
89+
90+
task.order = ++sGlobalTaskOrder;
91+
{
92+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
93+
task_queue_.push(task);
94+
95+
// Make sure the queue mutex is unlocked before waking up the loop. In case
96+
// the wake causes this thread to be descheduled for the primary thread to
97+
// process tasks, the acquisition of the lock on that thread while holding
98+
// the lock here momentarily till the end of the scope is a pessimization.
99+
}
100+
101+
WakeUp();
102+
}
103+
104+
} // namespace flutter

shell/platform/windows/task_runner.h

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_H_
66
#define FLUTTER_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_H_
77

8-
#include <windows.h>
9-
108
#include <chrono>
9+
#include <deque>
10+
#include <functional>
1111
#include <memory>
12+
#include <mutex>
13+
#include <queue>
14+
#include <variant>
1215

1316
#include "flutter/shell/platform/embedder/embedder.h"
1417

1518
namespace flutter {
1619

1720
typedef uint64_t (*CurrentTimeProc)();
1821

19-
// Abstract custom task runner for scheduling custom tasks.
2022
class TaskRunner {
2123
public:
2224
using TaskTimePoint = std::chrono::steady_clock::time_point;
@@ -25,18 +27,18 @@ class TaskRunner {
2527

2628
virtual ~TaskRunner() = default;
2729

28-
// Returns if the current thread is the UI thread.
30+
// Returns `true` if the current thread is this runner's thread.
2931
virtual bool RunsTasksOnCurrentThread() const = 0;
3032

3133
// Post a Flutter engine task to the event loop for delayed execution.
32-
virtual void PostFlutterTask(FlutterTask flutter_task,
33-
uint64_t flutter_target_time_nanos) = 0;
34+
void PostFlutterTask(FlutterTask flutter_task,
35+
uint64_t flutter_target_time_nanos);
3436

3537
// Post a task to the event loop.
36-
virtual void PostTask(TaskClosure task) = 0;
38+
void PostTask(TaskClosure task);
3739

3840
// Post a task to the event loop or run it immediately if this is being called
39-
// from the main thread.
41+
// from the runner's thread.
4042
void RunNowOrPostTask(TaskClosure task) {
4143
if (RunsTasksOnCurrentThread()) {
4244
task();
@@ -45,12 +47,57 @@ class TaskRunner {
4547
}
4648
}
4749

48-
// Creates a new task runner with the given main thread ID, current time
50+
// Creates a new task runner with the current thread, current time
4951
// provider, and callback for tasks that are ready to be run.
5052
static std::unique_ptr<TaskRunner> Create(
51-
DWORD main_thread_id,
5253
CurrentTimeProc get_current_time,
5354
const TaskExpiredCallback& on_task_expired);
55+
56+
protected:
57+
TaskRunner(CurrentTimeProc get_current_time,
58+
const TaskExpiredCallback& on_task_expired);
59+
60+
// Schedules timers to call `ProcessTasks()` at the runner's thread.
61+
virtual void WakeUp() = 0;
62+
63+
// Executes expired task, and returns the duration until the next task
64+
// deadline if exists, otherwise returns `std::chrono::nanoseconds::max()`.
65+
//
66+
// Each platform implementations must call this to schedule the tasks.
67+
std::chrono::nanoseconds ProcessTasks();
68+
69+
private:
70+
typedef std::variant<FlutterTask, TaskClosure> TaskVariant;
71+
72+
struct Task {
73+
uint64_t order;
74+
TaskTimePoint fire_time;
75+
TaskVariant variant;
76+
77+
struct Comparer {
78+
bool operator()(const Task& a, const Task& b) {
79+
if (a.fire_time == b.fire_time) {
80+
return a.order > b.order;
81+
}
82+
return a.fire_time > b.fire_time;
83+
}
84+
};
85+
};
86+
87+
// Enqueues the given task.
88+
void EnqueueTask(Task task);
89+
90+
// Returns a TaskTimePoint computed from the given target time from Flutter.
91+
TaskTimePoint TimePointFromFlutterTime(
92+
uint64_t flutter_target_time_nanos) const;
93+
94+
CurrentTimeProc get_current_time_;
95+
TaskExpiredCallback on_task_expired_;
96+
std::mutex task_queue_mutex_;
97+
std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;
98+
99+
TaskRunner(const TaskRunner&) = delete;
100+
TaskRunner& operator=(const TaskRunner&) = delete;
54101
};
55102

56103
} // namespace flutter
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <chrono>
6+
7+
#include "flutter/fml/time/time_point.h"
8+
9+
#include "flutter/shell/platform/windows/task_runner.h"
10+
11+
#include "gtest/gtest.h"
12+
13+
namespace flutter {
14+
namespace testing {
15+
16+
namespace {
17+
class MockTaskRunner : public TaskRunner {
18+
public:
19+
MockTaskRunner(CurrentTimeProc get_current_time,
20+
const TaskExpiredCallback& on_task_expired)
21+
: TaskRunner(get_current_time, on_task_expired) {}
22+
23+
virtual bool RunsTasksOnCurrentThread() const override { return true; }
24+
25+
void SimulateTimerAwake() { ProcessTasks(); }
26+
27+
protected:
28+
virtual void WakeUp() override {
29+
// Do nothing to avoid processing tasks immediately after the tasks is
30+
// posted.
31+
}
32+
};
33+
34+
uint64_t MockGetCurrentTime() {
35+
return static_cast<uint64_t>(
36+
fml::TimePoint::Now().ToEpochDelta().ToNanoseconds());
37+
}
38+
} // namespace
39+
40+
TEST(TaskRunnerTest, MaybeExecuteTaskWithExactOrder) {
41+
std::vector<uint64_t> executed_task_order;
42+
auto runner =
43+
MockTaskRunner(MockGetCurrentTime,
44+
[&executed_task_order](const FlutterTask* expired_task) {
45+
executed_task_order.push_back(expired_task->task);
46+
});
47+
48+
uint64_t time_now = MockGetCurrentTime();
49+
50+
runner.PostFlutterTask(FlutterTask{nullptr, 1}, time_now);
51+
runner.PostFlutterTask(FlutterTask{nullptr, 2}, time_now);
52+
runner.PostTask(
53+
[&executed_task_order]() { executed_task_order.push_back(3); });
54+
runner.PostTask(
55+
[&executed_task_order]() { executed_task_order.push_back(4); });
56+
57+
runner.SimulateTimerAwake();
58+
59+
std::vector<uint64_t> posted_task_order{1, 2, 3, 4};
60+
EXPECT_EQ(executed_task_order, posted_task_order);
61+
}
62+
63+
TEST(TaskRunnerTest, MaybeExecuteTaskOnlyExpired) {
64+
std::set<uint64_t> executed_task;
65+
auto runner = MockTaskRunner(
66+
MockGetCurrentTime, [&executed_task](const FlutterTask* expired_task) {
67+
executed_task.insert(expired_task->task);
68+
});
69+
70+
uint64_t time_now = MockGetCurrentTime();
71+
72+
uint64_t task_expired_before_now = 1;
73+
uint64_t time_before_now = time_now - 10000;
74+
runner.PostFlutterTask(FlutterTask{nullptr, task_expired_before_now},
75+
time_before_now);
76+
77+
uint64_t task_expired_after_now = 2;
78+
uint64_t time_after_now = time_now + 10000;
79+
runner.PostFlutterTask(FlutterTask{nullptr, task_expired_after_now},
80+
time_after_now);
81+
82+
runner.SimulateTimerAwake();
83+
84+
std::set<uint64_t> only_task_expired_before_now{task_expired_before_now};
85+
EXPECT_EQ(executed_task, only_task_expired_before_now);
86+
}
87+
88+
} // namespace testing
89+
} // namespace flutter

0 commit comments

Comments
 (0)