diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9a1fc038bebc0..0c85ab787829e 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -259,6 +259,8 @@ FILE: ../../../flutter/fml/posix_wrappers.h FILE: ../../../flutter/fml/raster_thread_merger.cc FILE: ../../../flutter/fml/raster_thread_merger.h FILE: ../../../flutter/fml/raster_thread_merger_unittests.cc +FILE: ../../../flutter/fml/shared_thread_merger.cc +FILE: ../../../flutter/fml/shared_thread_merger.h FILE: ../../../flutter/fml/size.h FILE: ../../../flutter/fml/status.h FILE: ../../../flutter/fml/synchronization/atomic_object.h diff --git a/fml/BUILD.gn b/fml/BUILD.gn index ff05896727cbf..5c8432e827bff 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -57,6 +57,8 @@ source_set("fml") { "posix_wrappers.h", "raster_thread_merger.cc", "raster_thread_merger.h", + "shared_thread_merger.cc", + "shared_thread_merger.h", "size.h", "synchronization/atomic_object.h", "synchronization/count_down_latch.cc", diff --git a/fml/memory/task_runner_checker.cc b/fml/memory/task_runner_checker.cc index 6390472234602..5320c49153b4d 100644 --- a/fml/memory/task_runner_checker.cc +++ b/fml/memory/task_runner_checker.cc @@ -8,7 +8,7 @@ namespace fml { TaskRunnerChecker::TaskRunnerChecker() : initialized_queue_id_(InitTaskQueueId()), - subsumed_queue_id_( + subsumed_queue_ids_( MessageLoopTaskQueues::GetInstance()->GetSubsumedTaskQueueId( initialized_queue_id_)){}; @@ -17,8 +17,15 @@ TaskRunnerChecker::~TaskRunnerChecker() = default; bool TaskRunnerChecker::RunsOnCreationTaskRunner() const { FML_CHECK(fml::MessageLoop::IsInitializedForCurrentThread()); const auto current_queue_id = MessageLoop::GetCurrentTaskQueueId(); - return RunsOnTheSameThread(current_queue_id, initialized_queue_id_) || - RunsOnTheSameThread(current_queue_id, subsumed_queue_id_); + if (RunsOnTheSameThread(current_queue_id, initialized_queue_id_)) { + return true; + } + for (auto& subsumed : subsumed_queue_ids_) { + if (RunsOnTheSameThread(current_queue_id, subsumed)) { + return true; + } + } + return false; }; bool TaskRunnerChecker::RunsOnTheSameThread(TaskQueueId queue_a, diff --git a/fml/memory/task_runner_checker.h b/fml/memory/task_runner_checker.h index 4732452408801..42dbd60cf411d 100644 --- a/fml/memory/task_runner_checker.h +++ b/fml/memory/task_runner_checker.h @@ -22,7 +22,7 @@ class TaskRunnerChecker final { private: TaskQueueId initialized_queue_id_; - TaskQueueId subsumed_queue_id_; + std::set subsumed_queue_ids_; TaskQueueId InitTaskQueueId(); }; diff --git a/fml/memory/task_runner_checker_unittest.cc b/fml/memory/task_runner_checker_unittest.cc index 81d168ca22230..6196384869a5a 100644 --- a/fml/memory/task_runner_checker_unittest.cc +++ b/fml/memory/task_runner_checker_unittest.cc @@ -94,14 +94,14 @@ TEST(TaskRunnerCheckerTests, MergedTaskRunnersRunsOnTheSameThread) { fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); const auto raster_thread_merger_ = fml::MakeRefCounted(qid1, qid2); - const int kNumFramesMerged = 5; + const size_t kNumFramesMerged = 5; raster_thread_merger_->MergeWithLease(kNumFramesMerged); // merged, running on the same thread EXPECT_EQ(TaskRunnerChecker::RunsOnTheSameThread(qid1, qid2), true); - for (int i = 0; i < kNumFramesMerged; i++) { + for (size_t i = 0; i < kNumFramesMerged; i++) { ASSERT_TRUE(raster_thread_merger_->IsMerged()); raster_thread_merger_->DecrementLease(); } @@ -154,7 +154,7 @@ TEST(TaskRunnerCheckerTests, }); latch3.Wait(); - fml::MessageLoopTaskQueues::GetInstance()->Unmerge(qid1); + fml::MessageLoopTaskQueues::GetInstance()->Unmerge(qid1, qid2); fml::AutoResetWaitableEvent latch4; loop2->GetTaskRunner()->PostTask([&]() { diff --git a/fml/memory/weak_ptr_unittest.cc b/fml/memory/weak_ptr_unittest.cc index e055ad9095409..2ed0b9ae008c5 100644 --- a/fml/memory/weak_ptr_unittest.cc +++ b/fml/memory/weak_ptr_unittest.cc @@ -212,14 +212,14 @@ TEST(TaskRunnerAffineWeakPtrTest, ShouldNotCrashIfRunningOnTheSameTaskRunner) { fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); const auto raster_thread_merger_ = fml::MakeRefCounted(qid1, qid2); - const int kNumFramesMerged = 5; + const size_t kNumFramesMerged = 5; raster_thread_merger_->MergeWithLease(kNumFramesMerged); loop2_task_start_latch.Signal(); loop2_task_finish_latch.Wait(); - for (int i = 0; i < kNumFramesMerged; i++) { + for (size_t i = 0; i < kNumFramesMerged; i++) { ASSERT_TRUE(raster_thread_merger_->IsMerged()); raster_thread_merger_->DecrementLease(); } diff --git a/fml/message_loop_task_queues.cc b/fml/message_loop_task_queues.cc index a2b4b600d26eb..e0146c6ef7c63 100644 --- a/fml/message_loop_task_queues.cc +++ b/fml/message_loop_task_queues.cc @@ -8,9 +8,9 @@ #include #include +#include #include "flutter/fml/make_copyable.h" -#include "flutter/fml/message_loop_impl.h" #include "flutter/fml/task_source.h" #include "flutter/fml/thread_local.h" @@ -25,7 +25,7 @@ fml::RefPtr MessageLoopTaskQueues::instance_; namespace { -// iOS prior to version 9 prevents c++11 thread_local and __thread specefier, +// iOS prior to version 9 prevents c++11 thread_local and __thread specifier, // having us resort to boxed enum containers. class TaskSourceGradeHolder { public: @@ -41,9 +41,7 @@ FML_THREAD_LOCAL ThreadLocalUniquePtr tls_task_source_grade; TaskQueueEntry::TaskQueueEntry(TaskQueueId created_for_arg) - : owner_of(_kUnmerged), - subsumed_by(_kUnmerged), - created_for(created_for_arg) { + : subsumed_by(_kUnmerged), created_for(created_for_arg) { wakeable = NULL; task_observers = TaskObservers(); task_source = std::make_unique(created_for); @@ -76,20 +74,21 @@ void MessageLoopTaskQueues::Dispose(TaskQueueId queue_id) { std::lock_guard guard(queue_mutex_); const auto& queue_entry = queue_entries_.at(queue_id); FML_DCHECK(queue_entry->subsumed_by == _kUnmerged); - TaskQueueId subsumed = queue_entry->owner_of; - queue_entries_.erase(queue_id); - if (subsumed != _kUnmerged) { + auto& subsumed_set = queue_entry->owner_of; + for (auto& subsumed : subsumed_set) { queue_entries_.erase(subsumed); } + // Erase owner queue_id at last to avoid &subsumed_set from being invalid + queue_entries_.erase(queue_id); } void MessageLoopTaskQueues::DisposeTasks(TaskQueueId queue_id) { std::lock_guard guard(queue_mutex_); const auto& queue_entry = queue_entries_.at(queue_id); FML_DCHECK(queue_entry->subsumed_by == _kUnmerged); - TaskQueueId subsumed = queue_entry->owner_of; + auto& subsumed_set = queue_entry->owner_of; queue_entry->task_source->ShutDown(); - if (subsumed != _kUnmerged) { + for (auto& subsumed : subsumed_set) { queue_entries_.at(subsumed)->task_source->ShutDown(); } } @@ -170,8 +169,8 @@ size_t MessageLoopTaskQueues::GetNumPendingTasks(TaskQueueId queue_id) const { size_t total_tasks = 0; total_tasks += queue_entry->task_source->GetNumPendingTasks(); - TaskQueueId subsumed = queue_entry->owner_of; - if (subsumed != _kUnmerged) { + auto& subsumed_set = queue_entry->owner_of; + for (auto& subsumed : subsumed_set) { const auto& subsumed_entry = queue_entries_.at(subsumed); total_tasks += subsumed_entry->task_source->GetNumPendingTasks(); } @@ -205,8 +204,8 @@ std::vector MessageLoopTaskQueues::GetObserversToNotify( observers.push_back(observer.second); } - TaskQueueId subsumed = queue_entries_.at(queue_id)->owner_of; - if (subsumed != _kUnmerged) { + auto& subsumed_set = queue_entries_.at(queue_id)->owner_of; + for (auto& subsumed : subsumed_set) { for (const auto& observer : queue_entries_.at(subsumed)->task_observers) { observers.push_back(observer.second); } @@ -230,22 +229,41 @@ bool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) { std::lock_guard guard(queue_mutex_); auto& owner_entry = queue_entries_.at(owner); auto& subsumed_entry = queue_entries_.at(subsumed); - - if (owner_entry->owner_of == subsumed) { + auto& subsumed_set = owner_entry->owner_of; + if (subsumed_set.find(subsumed) != subsumed_set.end()) { return true; } - std::vector owner_subsumed_keys = { - owner_entry->owner_of, owner_entry->subsumed_by, subsumed_entry->owner_of, - subsumed_entry->subsumed_by}; + // Won't check owner_entry->owner_of, because it may contains items when + // merged with other different queues. - for (auto key : owner_subsumed_keys) { - if (key != _kUnmerged) { - return false; - } + // Ensure owner_entry->subsumed_by being _kUnmerged + if (owner_entry->subsumed_by != _kUnmerged) { + FML_LOG(WARNING) << "Thread merging failed: owner_entry was already " + "subsumed by others, owner=" + << owner << ", subsumed=" << subsumed + << ", owner->subsumed_by=" << owner_entry->subsumed_by; + return false; } - - owner_entry->owner_of = subsumed; + // Ensure subsumed_entry->owner_of being empty + if (!subsumed_entry->owner_of.empty()) { + FML_LOG(WARNING) + << "Thread merging failed: subsumed_entry already owns others, owner=" + << owner << ", subsumed=" << subsumed + << ", subsumed->owner_of.size()=" << subsumed_entry->owner_of.size(); + return false; + } + // Ensure subsumed_entry->subsumed_by being _kUnmerged + if (subsumed_entry->subsumed_by != _kUnmerged) { + FML_LOG(WARNING) << "Thread merging failed: subsumed_entry was already " + "subsumed by others, owner=" + << owner << ", subsumed=" << subsumed + << ", subsumed->subsumed_by=" + << subsumed_entry->subsumed_by; + return false; + } + // All checking is OK, set merged state. + owner_entry->owner_of.insert(subsumed); subsumed_entry->subsumed_by = owner; if (HasPendingTasksUnlocked(owner)) { @@ -255,16 +273,37 @@ bool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) { return true; } -bool MessageLoopTaskQueues::Unmerge(TaskQueueId owner) { +bool MessageLoopTaskQueues::Unmerge(TaskQueueId owner, TaskQueueId subsumed) { std::lock_guard guard(queue_mutex_); const auto& owner_entry = queue_entries_.at(owner); - const TaskQueueId subsumed = owner_entry->owner_of; - if (subsumed == _kUnmerged) { + if (owner_entry->owner_of.empty()) { + FML_LOG(WARNING) + << "Thread unmerging failed: owner_entry doesn't own anyone, owner=" + << owner << ", subsumed=" << subsumed; + return false; + } + if (owner_entry->subsumed_by != _kUnmerged) { + FML_LOG(WARNING) + << "Thread unmerging failed: owner_entry was subsumed by others, owner=" + << owner << ", subsumed=" << subsumed + << ", owner_entry->subsumed_by=" << owner_entry->subsumed_by; + return false; + } + if (queue_entries_.at(subsumed)->subsumed_by == _kUnmerged) { + FML_LOG(WARNING) << "Thread unmerging failed: subsumed_entry wasn't " + "subsumed by others, owner=" + << owner << ", subsumed=" << subsumed; + return false; + } + if (owner_entry->owner_of.find(subsumed) == owner_entry->owner_of.end()) { + FML_LOG(WARNING) << "Thread unmerging failed: owner_entry didn't own the " + "given subsumed queue id, owner=" + << owner << ", subsumed=" << subsumed; return false; } queue_entries_.at(subsumed)->subsumed_by = _kUnmerged; - owner_entry->owner_of = _kUnmerged; + owner_entry->owner_of.erase(subsumed); if (HasPendingTasksUnlocked(owner)) { WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner)); @@ -280,11 +319,14 @@ bool MessageLoopTaskQueues::Unmerge(TaskQueueId owner) { bool MessageLoopTaskQueues::Owns(TaskQueueId owner, TaskQueueId subsumed) const { std::lock_guard guard(queue_mutex_); - return owner != _kUnmerged && subsumed != _kUnmerged && - subsumed == queue_entries_.at(owner)->owner_of; + if (owner == _kUnmerged || subsumed == _kUnmerged) { + return false; + } + auto& subsumed_set = queue_entries_.at(owner)->owner_of; + return subsumed_set.find(subsumed) != subsumed_set.end(); } -TaskQueueId MessageLoopTaskQueues::GetSubsumedTaskQueueId( +std::set MessageLoopTaskQueues::GetSubsumedTaskQueueId( TaskQueueId owner) const { std::lock_guard guard(queue_mutex_); return queue_entries_.at(owner)->owner_of; @@ -318,13 +360,11 @@ bool MessageLoopTaskQueues::HasPendingTasksUnlocked( return true; } - const TaskQueueId subsumed = entry->owner_of; - if (subsumed == _kUnmerged) { - // this is not an owner and queue is empty. - return false; - } else { - return !queue_entries_.at(subsumed)->task_source->IsEmpty(); - } + auto& subsumed_set = entry->owner_of; + return std::any_of( + subsumed_set.begin(), subsumed_set.end(), [&](const auto& subsumed) { + return !queue_entries_.at(subsumed)->task_source->IsEmpty(); + }); } fml::TimePoint MessageLoopTaskQueues::GetNextWakeTimeUnlocked( @@ -336,32 +376,35 @@ TaskSource::TopTask MessageLoopTaskQueues::PeekNextTaskUnlocked( TaskQueueId owner) const { FML_DCHECK(HasPendingTasksUnlocked(owner)); const auto& entry = queue_entries_.at(owner); - const TaskQueueId subsumed = entry->owner_of; - if (subsumed == _kUnmerged) { + if (entry->owner_of.empty()) { + FML_CHECK(!entry->task_source->IsEmpty()); return entry->task_source->Top(); } + // Use optional for the memory of TopTask object. + std::optional top_task; + + std::function top_task_updater = + [&top_task](const TaskSource* source) { + if (source && !source->IsEmpty()) { + TaskSource::TopTask other_task = source->Top(); + if (!top_task.has_value() || top_task->task > other_task.task) { + top_task.emplace(other_task); + } + } + }; + TaskSource* owner_tasks = entry->task_source.get(); - TaskSource* subsumed_tasks = queue_entries_.at(subsumed)->task_source.get(); - - // we are owning another task queue - const bool subsumed_has_task = !subsumed_tasks->IsEmpty(); - const bool owner_has_task = !owner_tasks->IsEmpty(); - fml::TaskQueueId top_queue_id = owner; - if (owner_has_task && subsumed_has_task) { - const auto owner_task = owner_tasks->Top(); - const auto subsumed_task = subsumed_tasks->Top(); - if (owner_task.task > subsumed_task.task) { - top_queue_id = subsumed; - } else { - top_queue_id = owner; - } - } else if (owner_has_task) { - top_queue_id = owner; - } else { - top_queue_id = subsumed; + top_task_updater(owner_tasks); + + for (TaskQueueId subsumed : entry->owner_of) { + TaskSource* subsumed_tasks = queue_entries_.at(subsumed)->task_source.get(); + top_task_updater(subsumed_tasks); } - return queue_entries_.at(top_queue_id)->task_source->Top(); + // At least one task at the top because PeekNextTaskUnlocked() is called after + // HasPendingTasksUnlocked() + FML_CHECK(top_task.has_value()); + return top_task.value(); } } // namespace fml diff --git a/fml/message_loop_task_queues.h b/fml/message_loop_task_queues.h index da3362f1c3792..703c6bcec3b58 100644 --- a/fml/message_loop_task_queues.h +++ b/fml/message_loop_task_queues.h @@ -7,6 +7,7 @@ #include #include +#include #include #include "flutter/fml/closure.h" @@ -34,11 +35,12 @@ class TaskQueueEntry { TaskObservers task_observers; std::unique_ptr task_source; - // Note: Both of these can be _kUnmerged, which indicates that - // this queue has not been merged or subsumed. OR exactly one - // of these will be _kUnmerged, if owner_of is _kUnmerged, it means - // that the queue has been subsumed or else it owns another queue. - TaskQueueId owner_of; + /// Set of the TaskQueueIds which is owned by this TaskQueue. If the set is + /// empty, this TaskQueue does not own any other TaskQueues. + std::set owner_of; + + /// Identifies the TaskQueue that subsumes this TaskQueue. If it is _kUnmerged + /// it indicates that this TaskQueue is not owned by any other TaskQueue. TaskQueueId subsumed_by; TaskQueueId created_for; @@ -108,25 +110,28 @@ class MessageLoopTaskQueues // to it. It is not aware of whether a queue is merged or not. Same with // task observers. // 2. When we get the tasks to run now, we look at both the queue_ids - // for the owner, subsumed will spin. - // 3. Each task queue can only be merged and subsumed once. + // for the owner and the subsumed task queues. + // 3. One TaskQueue can subsume multiple other TaskQueues. A TaskQueue can be + // in exactly one of the following three states: + // a. Be an owner of multiple other TaskQueues. + // b. Be subsumed by a TaskQueue (an owner can never be subsumed). + // c. Be independent, i.e, neither owner nor be subsumed. // // Methods currently aware of the merged state of the queues: // HasPendingTasks, GetNextTaskToRun, GetNumPendingTasks - - // This method returns false if either the owner or subsumed has already been - // merged with something else. bool Merge(TaskQueueId owner, TaskQueueId subsumed); - // Will return false if the owner has not been merged before. - bool Unmerge(TaskQueueId owner); + // Will return false if the owner has not been merged before, or owner was + // subsumed by others, or subsumed wasn't subsumed by others, or owner + // didn't own the given subsumed queue id. + bool Unmerge(TaskQueueId owner, TaskQueueId subsumed); /// Returns \p true if \p owner owns the \p subsumed task queue. bool Owns(TaskQueueId owner, TaskQueueId subsumed) const; // Returns the subsumed task queue if any or |TaskQueueId::kUnmerged| // otherwise. - TaskQueueId GetSubsumedTaskQueueId(TaskQueueId owner) const; + std::set GetSubsumedTaskQueueId(TaskQueueId owner) const; void PauseSecondarySource(TaskQueueId queue_id); diff --git a/fml/message_loop_task_queues_merge_unmerge_unittests.cc b/fml/message_loop_task_queues_merge_unmerge_unittests.cc index de28efeed0398..238028430f16a 100644 --- a/fml/message_loop_task_queues_merge_unmerge_unittests.cc +++ b/fml/message_loop_task_queues_merge_unmerge_unittests.cc @@ -100,34 +100,87 @@ TEST(MessageLoopTaskQueueMergeUnmerge, MergeUnmergeTasksPreserved) { ASSERT_EQ(2u, task_queue->GetNumPendingTasks(queue_id_1)); ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2)); - task_queue->Unmerge(queue_id_1); + task_queue->Unmerge(queue_id_1, queue_id_2); ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1)); ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2)); } -TEST(MessageLoopTaskQueueMergeUnmerge, MergeFailIfAlreadyMergedOrSubsumed) { +/// Multiple standalone engines scene +TEST(MessageLoopTaskQueueMergeUnmerge, + OneCanOwnMultipleQueuesAndUnmergeIndependently) { auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); - auto queue_id_1 = task_queue->CreateTaskQueue(); auto queue_id_2 = task_queue->CreateTaskQueue(); auto queue_id_3 = task_queue->CreateTaskQueue(); - task_queue->Merge(queue_id_1, queue_id_2); + // merge + ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2)); + ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2)); + ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3)); + + ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_3)); + ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2)); + ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3)); + + // unmerge + ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_2)); + ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2)); + ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3)); + + ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_3)); + ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2)); + ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3)); +} + +TEST(MessageLoopTaskQueueMergeUnmerge, + CannotMergeSameQueueToTwoDifferentOwners) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + auto queue = task_queue->CreateTaskQueue(); + auto owner_1 = task_queue->CreateTaskQueue(); + auto owner_2 = task_queue->CreateTaskQueue(); + + ASSERT_TRUE(task_queue->Merge(owner_1, queue)); + ASSERT_FALSE(task_queue->Merge(owner_2, queue)); +} + +TEST(MessageLoopTaskQueueMergeUnmerge, MergeFailIfAlreadySubsumed) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); - ASSERT_FALSE(task_queue->Merge(queue_id_1, queue_id_3)); + auto queue_id_1 = task_queue->CreateTaskQueue(); + auto queue_id_2 = task_queue->CreateTaskQueue(); + auto queue_id_3 = task_queue->CreateTaskQueue(); + + ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2)); ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_3)); + ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_1)); } -TEST(MessageLoopTaskQueueMergeUnmerge, UnmergeFailsOnSubsumed) { +TEST(MessageLoopTaskQueueMergeUnmerge, + MergeFailIfAlreadyOwnsButTryToBeSubsumed) { auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); auto queue_id_1 = task_queue->CreateTaskQueue(); auto queue_id_2 = task_queue->CreateTaskQueue(); + auto queue_id_3 = task_queue->CreateTaskQueue(); task_queue->Merge(queue_id_1, queue_id_2); + // A recursively linked merging will fail + ASSERT_FALSE(task_queue->Merge(queue_id_3, queue_id_1)); +} - ASSERT_FALSE(task_queue->Unmerge(queue_id_2)); +TEST(MessageLoopTaskQueueMergeUnmerge, UnmergeFailsOnSubsumedOrNeverMerged) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + + auto queue_id_1 = task_queue->CreateTaskQueue(); + auto queue_id_2 = task_queue->CreateTaskQueue(); + auto queue_id_3 = task_queue->CreateTaskQueue(); + + task_queue->Merge(queue_id_1, queue_id_2); + ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_3)); + ASSERT_FALSE(task_queue->Unmerge(queue_id_1, queue_id_3)); + ASSERT_FALSE(task_queue->Unmerge(queue_id_3, queue_id_1)); + ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_1)); } TEST(MessageLoopTaskQueueMergeUnmerge, MergeInvokesBothWakeables) { @@ -177,7 +230,7 @@ TEST(MessageLoopTaskQueueMergeUnmerge, queue_id_2, []() {}, ChronoTicksSinceEpoch()); task_queue->Merge(queue_id_1, queue_id_2); - task_queue->Unmerge(queue_id_1); + task_queue->Unmerge(queue_id_1, queue_id_2); CountRemainingTasks(task_queue, queue_id_1); diff --git a/fml/message_loop_task_queues_unittests.cc b/fml/message_loop_task_queues_unittests.cc index 9e571d4d4177b..48f6fb871ef90 100644 --- a/fml/message_loop_task_queues_unittests.cc +++ b/fml/message_loop_task_queues_unittests.cc @@ -63,6 +63,35 @@ TEST(MessageLoopTaskQueue, RegisterTwoTasksAndCount) { ASSERT_TRUE(task_queue->GetNumPendingTasks(queue_id) == 2); } +TEST(MessageLoopTaskQueue, RegisterTasksOnMergedQueuesAndCount) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + auto platform_queue = task_queue->CreateTaskQueue(); + auto raster_queue = task_queue->CreateTaskQueue(); + // A task in platform_queue + task_queue->RegisterTask( + platform_queue, []() {}, fml::TimePoint::Now()); + // A task in raster_queue + task_queue->RegisterTask( + raster_queue, []() {}, fml::TimePoint::Now()); + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1); + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1); + + ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue)); + task_queue->Merge(platform_queue, raster_queue); + ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue)); + + ASSERT_TRUE(task_queue->HasPendingTasks(platform_queue)); + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 2); + // The task count of subsumed queue is 0 + ASSERT_FALSE(task_queue->HasPendingTasks(raster_queue)); + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0); + + task_queue->Unmerge(platform_queue, raster_queue); + ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue)); + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1); + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1); +} + TEST(MessageLoopTaskQueue, PreserveTaskOrdering) { auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); auto queue_id = task_queue->CreateTaskQueue(); @@ -78,7 +107,7 @@ TEST(MessageLoopTaskQueue, PreserveTaskOrdering) { const auto now = ChronoTicksSinceEpoch(); int expected_value = 1; - for (;;) { + while (true) { fml::closure invocation = task_queue->GetNextTaskToRun(queue_id, now); if (!invocation) { break; @@ -89,6 +118,123 @@ TEST(MessageLoopTaskQueue, PreserveTaskOrdering) { } } +TEST(MessageLoopTaskQueue, RegisterTasksOnMergedQueuesPreserveTaskOrdering) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + auto platform_queue = task_queue->CreateTaskQueue(); + auto raster1_queue = task_queue->CreateTaskQueue(); + auto raster2_queue = task_queue->CreateTaskQueue(); + int test_val = 0; + + // order 0 in raster1_queue + task_queue->RegisterTask( + raster1_queue, [&test_val]() { test_val = 0; }, fml::TimePoint::Now()); + + // order 1 in platform_queue + task_queue->RegisterTask( + platform_queue, [&test_val]() { test_val = 1; }, fml::TimePoint::Now()); + + // order 2 in raster2_queue + task_queue->RegisterTask( + raster2_queue, [&test_val]() { test_val = 2; }, fml::TimePoint::Now()); + + task_queue->Merge(platform_queue, raster1_queue); + ASSERT_TRUE(task_queue->Owns(platform_queue, raster1_queue)); + task_queue->Merge(platform_queue, raster2_queue); + ASSERT_TRUE(task_queue->Owns(platform_queue, raster2_queue)); + const auto now = fml::TimePoint::Now(); + int expected_value = 0; + // Right order: + // "test_val = 0" in raster1_queue + // "test_val = 1" in platform_queue + // "test_val = 2" in raster2_queue + while (true) { + fml::closure invocation = task_queue->GetNextTaskToRun(platform_queue, now); + if (!invocation) { + break; + } + invocation(); + ASSERT_TRUE(test_val == expected_value); + expected_value++; + } +} + +TEST(MessageLoopTaskQueue, UnmergeRespectTheOriginalTaskOrderingInQueues) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + auto platform_queue = task_queue->CreateTaskQueue(); + auto raster_queue = task_queue->CreateTaskQueue(); + int test_val = 0; + + // order 0 in platform_queue + task_queue->RegisterTask( + platform_queue, [&test_val]() { test_val = 0; }, fml::TimePoint::Now()); + // order 1 in platform_queue + task_queue->RegisterTask( + platform_queue, [&test_val]() { test_val = 1; }, fml::TimePoint::Now()); + // order 2 in raster_queue + task_queue->RegisterTask( + raster_queue, [&test_val]() { test_val = 2; }, fml::TimePoint::Now()); + // order 3 in raster_queue + task_queue->RegisterTask( + raster_queue, [&test_val]() { test_val = 3; }, fml::TimePoint::Now()); + // order 4 in platform_queue + task_queue->RegisterTask( + platform_queue, [&test_val]() { test_val = 4; }, fml::TimePoint::Now()); + // order 5 in raster_queue + task_queue->RegisterTask( + raster_queue, [&test_val]() { test_val = 5; }, fml::TimePoint::Now()); + + ASSERT_TRUE(task_queue->Merge(platform_queue, raster_queue)); + ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue)); + const auto now = fml::TimePoint::Now(); + // The right order after merged and consumed 3 tasks: + // "test_val = 0" in platform_queue + // "test_val = 1" in platform_queue + // "test_val = 2" in raster_queue (running on platform) + for (int i = 0; i < 3; i++) { + fml::closure invocation = task_queue->GetNextTaskToRun(platform_queue, now); + ASSERT_FALSE(!invocation); + invocation(); + ASSERT_TRUE(test_val == i); + } + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 3); + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0); + + ASSERT_TRUE(task_queue->Unmerge(platform_queue, raster_queue)); + ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue)); + + // The right order after unmerged and left 3 tasks: + // "test_val = 3" in raster_queue + // "test_val = 4" in platform_queue + // "test_val = 5" in raster_queue + + // platform_queue has 1 task left: "test_val = 4" + { + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1); + fml::closure invocation = task_queue->GetNextTaskToRun(platform_queue, now); + ASSERT_FALSE(!invocation); + invocation(); + ASSERT_TRUE(test_val == 4); + ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 0); + } + + // raster_queue has 2 tasks left: "test_val = 3" and "test_val = 5" + { + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 2); + fml::closure invocation = task_queue->GetNextTaskToRun(raster_queue, now); + ASSERT_FALSE(!invocation); + invocation(); + ASSERT_TRUE(test_val == 3); + } + { + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1); + fml::closure invocation = task_queue->GetNextTaskToRun(raster_queue, now); + ASSERT_FALSE(!invocation); + invocation(); + ASSERT_TRUE(test_val == 5); + ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0); + } +} + void TestNotifyObservers(fml::TaskQueueId queue_id) { auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); std::vector observers = @@ -195,6 +341,17 @@ TEST(MessageLoopTaskQueue, QueueDoNotOwnUnmergedTaskQueueId) { ASSERT_FALSE(task_queue->Owns(_kUnmerged, _kUnmerged)); } +TEST(MessageLoopTaskQueue, QueueOwnsMergedTaskQueueId) { + auto task_queue = fml::MessageLoopTaskQueues::GetInstance(); + auto platform_queue = task_queue->CreateTaskQueue(); + auto raster_queue = task_queue->CreateTaskQueue(); + ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue)); + ASSERT_FALSE(task_queue->Owns(raster_queue, platform_queue)); + task_queue->Merge(platform_queue, raster_queue); + ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue)); + ASSERT_FALSE(task_queue->Owns(raster_queue, platform_queue)); +} + //------------------------------------------------------------------------------ /// Verifies that tasks can be added to task queues concurrently. /// diff --git a/fml/raster_thread_merger.cc b/fml/raster_thread_merger.cc index cfeeea3dcb527..f14d64ed8ac28 100644 --- a/fml/raster_thread_merger.cc +++ b/fml/raster_thread_merger.cc @@ -10,24 +10,48 @@ namespace fml { -const int RasterThreadMerger::kLeaseNotSet = -1; - RasterThreadMerger::RasterThreadMerger(fml::TaskQueueId platform_queue_id, fml::TaskQueueId gpu_queue_id) + : RasterThreadMerger( + MakeRefCounted(platform_queue_id, gpu_queue_id), + platform_queue_id, + gpu_queue_id) {} + +RasterThreadMerger::RasterThreadMerger( + fml::RefPtr shared_merger, + fml::TaskQueueId platform_queue_id, + fml::TaskQueueId gpu_queue_id) : platform_queue_id_(platform_queue_id), gpu_queue_id_(gpu_queue_id), - task_queues_(fml::MessageLoopTaskQueues::GetInstance()), - lease_term_(kLeaseNotSet), - enabled_(true) { - FML_CHECK(!task_queues_->Owns(platform_queue_id_, gpu_queue_id_)); -} + shared_merger_(shared_merger), + enabled_(true) {} void RasterThreadMerger::SetMergeUnmergeCallback(const fml::closure& callback) { merge_unmerge_callback_ = callback; } +const fml::RefPtr& +RasterThreadMerger::GetSharedRasterThreadMerger() const { + return shared_merger_; +} + +fml::RefPtr +RasterThreadMerger::CreateOrShareThreadMerger( + const fml::RefPtr& parent_merger, + TaskQueueId platform_id, + TaskQueueId raster_id) { + if (parent_merger && parent_merger->platform_queue_id_ == platform_id && + parent_merger->gpu_queue_id_ == raster_id) { + auto shared_merger = parent_merger->GetSharedRasterThreadMerger(); + return fml::MakeRefCounted(shared_merger, platform_id, + raster_id); + } else { + return fml::MakeRefCounted(platform_id, raster_id); + } +} + void RasterThreadMerger::MergeWithLease(size_t lease_term) { - std::scoped_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); if (TaskQueuesAreSame()) { return; } @@ -41,30 +65,27 @@ void RasterThreadMerger::MergeWithLease(size_t lease_term) { return; } - bool success = task_queues_->Merge(platform_queue_id_, gpu_queue_id_); + bool success = shared_merger_->MergeWithLease(this, lease_term); if (success && merge_unmerge_callback_ != nullptr) { merge_unmerge_callback_(); } - FML_CHECK(success) << "Unable to merge the raster and platform threads."; - lease_term_ = lease_term; merged_condition_.notify_one(); } -void RasterThreadMerger::UnMergeNow() { - std::scoped_lock lock(lease_term_mutex_); +void RasterThreadMerger::UnMergeNowIfLastOne() { + std::scoped_lock lock(mutex_); + if (TaskQueuesAreSame()) { return; } if (!IsEnabledUnSafe()) { return; } - lease_term_ = 0; - bool success = task_queues_->Unmerge(platform_queue_id_); + bool success = shared_merger_->UnMergeNowIfLastOne(this); if (success && merge_unmerge_callback_ != nullptr) { merge_unmerge_callback_(); } - FML_CHECK(success) << "Unable to un-merge the raster and platform threads."; } bool RasterThreadMerger::IsOnPlatformThread() const { @@ -80,34 +101,34 @@ bool RasterThreadMerger::IsOnRasterizingThread() const { } void RasterThreadMerger::ExtendLeaseTo(size_t lease_term) { + FML_DCHECK(lease_term > 0) << "lease_term should be positive."; if (TaskQueuesAreSame()) { return; } - std::scoped_lock lock(lease_term_mutex_); - FML_DCHECK(IsMergedUnSafe()) << "lease_term should be positive."; - if (lease_term_ != kLeaseNotSet && - static_cast(lease_term) > lease_term_) { - lease_term_ = lease_term; + std::scoped_lock lock(mutex_); + if (!IsEnabledUnSafe()) { + return; } + shared_merger_->ExtendLeaseTo(this, lease_term); } bool RasterThreadMerger::IsMerged() { - std::scoped_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); return IsMergedUnSafe(); } void RasterThreadMerger::Enable() { - std::scoped_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); enabled_ = true; } void RasterThreadMerger::Disable() { - std::scoped_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); enabled_ = false; } bool RasterThreadMerger::IsEnabled() { - std::scoped_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); return IsEnabledUnSafe(); } @@ -116,7 +137,7 @@ bool RasterThreadMerger::IsEnabledUnSafe() const { } bool RasterThreadMerger::IsMergedUnSafe() const { - return lease_term_ > 0 || TaskQueuesAreSame(); + return TaskQueuesAreSame() || shared_merger_->IsMergedUnSafe(); } bool RasterThreadMerger::TaskQueuesAreSame() const { @@ -128,7 +149,7 @@ void RasterThreadMerger::WaitUntilMerged() { return; } FML_CHECK(IsOnPlatformThread()); - std::unique_lock lock(lease_term_mutex_); + std::unique_lock lock(mutex_); merged_condition_.wait(lock, [&] { return IsMergedUnSafe(); }); } @@ -136,20 +157,18 @@ RasterThreadStatus RasterThreadMerger::DecrementLease() { if (TaskQueuesAreSame()) { return RasterThreadStatus::kRemainsMerged; } - std::unique_lock lock(lease_term_mutex_); + std::scoped_lock lock(mutex_); if (!IsMergedUnSafe()) { return RasterThreadStatus::kRemainsUnmerged; } if (!IsEnabledUnSafe()) { return RasterThreadStatus::kRemainsMerged; } - FML_DCHECK(lease_term_ > 0) - << "lease_term should always be positive when merged."; - lease_term_--; - if (lease_term_ == 0) { - // |UnMergeNow| is going to acquire the lock again. - lock.unlock(); - UnMergeNow(); + bool unmerged_after_decrement = shared_merger_->DecrementLease(this); + if (unmerged_after_decrement) { + if (merge_unmerge_callback_ != nullptr) { + merge_unmerge_callback_(); + } return RasterThreadStatus::kUnmergedNow; } diff --git a/fml/raster_thread_merger.h b/fml/raster_thread_merger.h index 9e38b23a555ee..bcad1904e1513 100644 --- a/fml/raster_thread_merger.h +++ b/fml/raster_thread_merger.h @@ -11,6 +11,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/memory/ref_counted.h" #include "flutter/fml/message_loop_task_queues.h" +#include "flutter/fml/shared_thread_merger.h" namespace fml { @@ -22,6 +23,11 @@ enum class RasterThreadStatus { kUnmergedNow }; +/// This class is a client and proxy between the rasterizer and +/// |SharedThreadMerger|. The multiple |RasterThreadMerger| instances with same +/// owner_queue_id and same subsumed_queue_id share the same +/// |SharedThreadMerger| instance. Whether they share the same inner instance is +/// determined by |RasterThreadMerger::CreateOrShareThreadMerger| method. class RasterThreadMerger : public fml::RefCountedThreadSafe { public: @@ -36,14 +42,28 @@ class RasterThreadMerger // When task queues are statically merged this method becomes no-op. void MergeWithLease(size_t lease_term); - // Un-merges the threads now, and resets the lease term to 0. + // Gets the shared merger from current merger object + const fml::RefPtr& GetSharedRasterThreadMerger() const; + + /// Creates a new merger from parent, share the inside shared_merger member + /// when the platform_queue_id and raster_queue_id are same, otherwise create + /// a new shared_merger instance + static fml::RefPtr CreateOrShareThreadMerger( + const fml::RefPtr& parent_merger, + TaskQueueId platform_id, + TaskQueueId raster_id); + + // Un-merges the threads now if current caller is the last merge caller, + // and it resets the lease term to 0, otherwise it will remove the caller + // record and return. The multiple caller records were recorded after + // |MergeWithLease| or |ExtendLeaseTo| method. // // Must be executed on the raster task runner. // // If the task queues are the same, we consider them statically merged. // When task queues are statically merged, we never unmerge them and // this method becomes no-op. - void UnMergeNow(); + void UnMergeNowIfLastOne(); // If the task queues are the same, we consider them statically merged. // When task queues are statically merged this method becomes no-op. @@ -56,6 +76,9 @@ class RasterThreadMerger // When task queues are statically merged this method becomes no-op. RasterThreadStatus DecrementLease(); + // The method is locked by current instance, and asks the shared instance of + // SharedThreadMerger and the merging state is determined by the + // lease_term_ counter. bool IsMerged(); // Waits until the threads are merged. @@ -63,9 +86,6 @@ class RasterThreadMerger // Must run on the platform task runner. void WaitUntilMerged(); - RasterThreadMerger(fml::TaskQueueId platform_queue_id, - fml::TaskQueueId gpu_queue_id); - // Returns true if the current thread owns rasterizing. // When the threads are merged, platform thread owns rasterizing. // When un-merged, raster thread owns rasterizing. @@ -78,12 +98,12 @@ class RasterThreadMerger void Enable(); // Disables the thread merger. Once disabled, any call to - // |MergeWithLease| or |UnMergeNow| results in a noop. + // |MergeWithLease| or |UnMergeNowIfLastOne| results in a noop. void Disable(); // Whether the thread merger is enabled. By default, the thread merger is - // enabled. If false, calls to |MergeWithLease| or |UnMergeNow| results in a - // noop. + // enabled. If false, calls to |MergeWithLease| or |UnMergeNowIfLastOne| + // or |ExtendLeaseTo| or |DecrementLease| results in a noop. bool IsEnabled(); // Registers a callback that can be used to clean up global state right after @@ -94,13 +114,18 @@ class RasterThreadMerger void SetMergeUnmergeCallback(const fml::closure& callback); private: - static const int kLeaseNotSet; fml::TaskQueueId platform_queue_id_; fml::TaskQueueId gpu_queue_id_; - fml::RefPtr task_queues_; - std::atomic_int lease_term_; + + RasterThreadMerger(fml::TaskQueueId platform_queue_id, + fml::TaskQueueId gpu_queue_id); + RasterThreadMerger(fml::RefPtr shared_merger, + fml::TaskQueueId platform_queue_id, + fml::TaskQueueId gpu_queue_id); + + const fml::RefPtr shared_merger_; std::condition_variable merged_condition_; - std::mutex lease_term_mutex_; + std::mutex mutex_; fml::closure merge_unmerge_callback_; bool enabled_; diff --git a/fml/raster_thread_merger_unittests.cc b/fml/raster_thread_merger_unittests.cc index 5c7b6fd4bfffc..339cd174752dd 100644 --- a/fml/raster_thread_merger_unittests.cc +++ b/fml/raster_thread_merger_unittests.cc @@ -6,7 +6,6 @@ #include "flutter/fml/raster_thread_merger.h" -#include #include #include "flutter/fml/memory/ref_ptr.h" @@ -20,51 +19,61 @@ namespace fml { namespace testing { -TEST(RasterThreadMerger, RemainMergedTillLeaseExpires) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); +/// A mock task queue NOT calling MessageLoop->Run() in thread +struct TaskQueueWrapper { + fml::MessageLoop* loop = nullptr; + + /// The waiter for message loop initialized ok + fml::AutoResetWaitableEvent latch; + + /// The waiter for thread finished + fml::AutoResetWaitableEvent term; + + /// This field must below latch and term member, because + /// cpp standard reference: + /// non-static data members are initialized in the order they were declared in + /// the class definition + std::thread thread; + + TaskQueueWrapper() + : thread([this]() { + fml::MessageLoop::EnsureInitializedForCurrentThread(); + loop = &fml::MessageLoop::GetCurrent(); + latch.Signal(); + term.Wait(); + }) { + latch.Wait(); + } - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); + ~TaskQueueWrapper() { + term.Signal(); + thread.join(); + } - latch1.Wait(); - latch2.Wait(); + fml::TaskQueueId GetTaskQueueId() const { + return loop->GetTaskRunner()->GetTaskQueueId(); + } +}; - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = +TEST(RasterThreadMerger, RemainMergedTillLeaseExpires) { + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); - const int kNumFramesMerged = 5; + const size_t kNumFramesMerged = 5; - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->MergeWithLease(kNumFramesMerged); + raster_thread_merger->MergeWithLease(kNumFramesMerged); - for (int i = 0; i < kNumFramesMerged; i++) { - ASSERT_TRUE(raster_thread_merger_->IsMerged()); - raster_thread_merger_->DecrementLease(); + for (size_t i = 0; i < kNumFramesMerged; i++) { + ASSERT_TRUE(raster_thread_merger->IsMerged()); + raster_thread_merger->DecrementLease(); } - ASSERT_FALSE(raster_thread_merger_->IsMerged()); - - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); } TEST(RasterThreadMerger, IsNotOnRasterizingThread) { @@ -91,32 +100,32 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); fml::CountDownLatch pre_merge(2), post_merge(2), post_unmerge(2); loop1->GetTaskRunner()->PostTask([&]() { - ASSERT_FALSE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_FALSE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); pre_merge.CountDown(); }); loop2->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2); pre_merge.CountDown(); }); pre_merge.Wait(); - raster_thread_merger_->MergeWithLease(1); + raster_thread_merger->MergeWithLease(1); loop1->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_merge.CountDown(); }); @@ -124,26 +133,26 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { loop2->GetTaskRunner()->PostTask([&]() { // this will be false since this is going to be run // on loop1 really. - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_merge.CountDown(); }); post_merge.Wait(); - raster_thread_merger_->DecrementLease(); + raster_thread_merger->DecrementLease(); loop1->GetTaskRunner()->PostTask([&]() { - ASSERT_FALSE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_FALSE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_unmerge.CountDown(); }); loop2->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2); post_unmerge.CountDown(); }); @@ -159,60 +168,35 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { } TEST(RasterThreadMerger, LeaseExtension) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); - - latch1.Wait(); - latch2.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); - const int kNumFramesMerged = 5; + const size_t kNumFramesMerged = 5; - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->MergeWithLease(kNumFramesMerged); + raster_thread_merger->MergeWithLease(kNumFramesMerged); // let there be one more turn till the leases expire. - for (int i = 0; i < kNumFramesMerged - 1; i++) { - ASSERT_TRUE(raster_thread_merger_->IsMerged()); - raster_thread_merger_->DecrementLease(); + for (size_t i = 0; i < kNumFramesMerged - 1; i++) { + ASSERT_TRUE(raster_thread_merger->IsMerged()); + raster_thread_merger->DecrementLease(); } // extend the lease once. - raster_thread_merger_->ExtendLeaseTo(kNumFramesMerged); + raster_thread_merger->ExtendLeaseTo(kNumFramesMerged); // we will NOT last for 1 extra turn, we just set it. - for (int i = 0; i < kNumFramesMerged; i++) { - ASSERT_TRUE(raster_thread_merger_->IsMerged()); - raster_thread_merger_->DecrementLease(); + for (size_t i = 0; i < kNumFramesMerged; i++) { + ASSERT_TRUE(raster_thread_merger->IsMerged()); + raster_thread_merger->DecrementLease(); } - ASSERT_FALSE(raster_thread_merger_->IsMerged()); - - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); } TEST(RasterThreadMerger, WaitUntilMerged) { @@ -233,7 +217,7 @@ TEST(RasterThreadMerger, WaitUntilMerged) { term_platform.Wait(); }); - const int kNumFramesMerged = 5; + const size_t kNumFramesMerged = 5; fml::MessageLoop* loop_raster = nullptr; fml::AutoResetWaitableEvent term_raster; std::thread thread_raster([&]() { @@ -255,7 +239,7 @@ TEST(RasterThreadMerger, WaitUntilMerged) { latch_merged.Wait(); ASSERT_TRUE(raster_thread_merger->IsMerged()); - for (int i = 0; i < kNumFramesMerged; i++) { + for (size_t i = 0; i < kNumFramesMerged; i++) { ASSERT_TRUE(raster_thread_merger->IsMerged()); raster_thread_merger->DecrementLease(); } @@ -269,204 +253,112 @@ TEST(RasterThreadMerger, WaitUntilMerged) { } TEST(RasterThreadMerger, HandleTaskQueuesAreTheSame) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); - - latch1.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); + TaskQueueWrapper queue; + fml::TaskQueueId qid1 = queue.GetTaskQueueId(); fml::TaskQueueId qid2 = qid1; - const auto raster_thread_merger_ = + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); // Statically merged. - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + ASSERT_TRUE(raster_thread_merger->IsMerged()); // Test decrement lease and unmerge are both no-ops. // The task queues should be always merged. - const int kNumFramesMerged = 5; - raster_thread_merger_->MergeWithLease(kNumFramesMerged); + const size_t kNumFramesMerged = 5; + raster_thread_merger->MergeWithLease(kNumFramesMerged); - for (int i = 0; i < kNumFramesMerged; i++) { - ASSERT_TRUE(raster_thread_merger_->IsMerged()); - raster_thread_merger_->DecrementLease(); + for (size_t i = 0; i < kNumFramesMerged; i++) { + ASSERT_TRUE(raster_thread_merger->IsMerged()); + raster_thread_merger->DecrementLease(); } - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + ASSERT_TRUE(raster_thread_merger->IsMerged()); // Wait until merged should also return immediately. - raster_thread_merger_->WaitUntilMerged(); - ASSERT_TRUE(raster_thread_merger_->IsMerged()); - - term1.Signal(); - thread1.join(); + raster_thread_merger->WaitUntilMerged(); + ASSERT_TRUE(raster_thread_merger->IsMerged()); } TEST(RasterThreadMerger, Enable) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); - - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); - - latch1.Wait(); - latch2.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); - raster_thread_merger_->Disable(); - raster_thread_merger_->MergeWithLease(1); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); - - raster_thread_merger_->Enable(); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Disable(); + raster_thread_merger->MergeWithLease(1); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->MergeWithLease(1); - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Enable(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->DecrementLease(); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + raster_thread_merger->MergeWithLease(1); + ASSERT_TRUE(raster_thread_merger->IsMerged()); - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); + raster_thread_merger->DecrementLease(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); } TEST(RasterThreadMerger, Disable) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); - - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); - - latch1.Wait(); - latch2.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); - raster_thread_merger_->Disable(); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Disable(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->MergeWithLease(1); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + raster_thread_merger->MergeWithLease(1); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->Enable(); - raster_thread_merger_->MergeWithLease(1); - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Enable(); + raster_thread_merger->MergeWithLease(1); + ASSERT_TRUE(raster_thread_merger->IsMerged()); - raster_thread_merger_->Disable(); - raster_thread_merger_->UnMergeNow(); - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Disable(); + raster_thread_merger->UnMergeNowIfLastOne(); + ASSERT_TRUE(raster_thread_merger->IsMerged()); { - auto decrement_result = raster_thread_merger_->DecrementLease(); + auto decrement_result = raster_thread_merger->DecrementLease(); ASSERT_EQ(fml::RasterThreadStatus::kRemainsMerged, decrement_result); } - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + ASSERT_TRUE(raster_thread_merger->IsMerged()); - raster_thread_merger_->Enable(); - raster_thread_merger_->UnMergeNow(); - ASSERT_FALSE(raster_thread_merger_->IsMerged()); + raster_thread_merger->Enable(); + raster_thread_merger->UnMergeNowIfLastOne(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); - raster_thread_merger_->MergeWithLease(1); + raster_thread_merger->MergeWithLease(1); - ASSERT_TRUE(raster_thread_merger_->IsMerged()); + ASSERT_TRUE(raster_thread_merger->IsMerged()); { - auto decrement_result = raster_thread_merger_->DecrementLease(); + auto decrement_result = raster_thread_merger->DecrementLease(); ASSERT_EQ(fml::RasterThreadStatus::kUnmergedNow, decrement_result); } - ASSERT_FALSE(raster_thread_merger_->IsMerged()); - - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); } TEST(RasterThreadMerger, IsEnabled) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); - - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); - - latch1.Wait(); - latch2.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); - const auto raster_thread_merger_ = + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); - ASSERT_TRUE(raster_thread_merger_->IsEnabled()); + ASSERT_TRUE(raster_thread_merger->IsEnabled()); - raster_thread_merger_->Disable(); - ASSERT_FALSE(raster_thread_merger_->IsEnabled()); + raster_thread_merger->Disable(); + ASSERT_FALSE(raster_thread_merger->IsEnabled()); - raster_thread_merger_->Enable(); - ASSERT_TRUE(raster_thread_merger_->IsEnabled()); - - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); + raster_thread_merger->Enable(); + ASSERT_TRUE(raster_thread_merger->IsEnabled()); } TEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskMergesThreads) { @@ -491,21 +383,21 @@ TEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskMergesThreads) { fml::TaskQueueId qid_raster = loop_raster->GetTaskRunner()->GetTaskQueueId(); fml::CountDownLatch post_merge(2); - const auto raster_thread_merger_ = + const auto raster_thread_merger = fml::MakeRefCounted(qid_platform, qid_raster); loop_raster->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_raster); - raster_thread_merger_->MergeWithLease(1); + raster_thread_merger->MergeWithLease(1); post_merge.CountDown(); }); loop_raster->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_platform); - raster_thread_merger_->DecrementLease(); + raster_thread_merger->DecrementLease(); post_merge.CountDown(); }); @@ -536,10 +428,10 @@ TEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskUnMergesThreads) { loop_raster->GetTaskRunner()->GetTaskQueueId(); fml::AutoResetWaitableEvent merge_latch; - const auto raster_thread_merger_ = + const auto raster_thread_merger = fml::MakeRefCounted(qid_platform, qid_raster); loop_raster->GetTaskRunner()->PostTask([&]() { - raster_thread_merger_->MergeWithLease(1); + raster_thread_merger->MergeWithLease(1); merge_latch.Signal(); }); @@ -549,17 +441,17 @@ TEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskUnMergesThreads) { // threads should be merged at this point. fml::AutoResetWaitableEvent unmerge_latch; loop_raster->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_platform); - raster_thread_merger_->DecrementLease(); + raster_thread_merger->DecrementLease(); unmerge_latch.Signal(); }); fml::AutoResetWaitableEvent post_unmerge_latch; loop_raster->GetTaskRunner()->PostTask([&]() { - ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); - ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); + ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_raster); post_unmerge_latch.Signal(); }); @@ -576,31 +468,10 @@ TEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskUnMergesThreads) { } TEST(RasterThreadMerger, SetMergeUnmergeCallback) { - fml::MessageLoop* loop1 = nullptr; - fml::AutoResetWaitableEvent latch1; - fml::AutoResetWaitableEvent term1; - std::thread thread1([&loop1, &latch1, &term1]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop1 = &fml::MessageLoop::GetCurrent(); - latch1.Signal(); - term1.Wait(); - }); - - fml::MessageLoop* loop2 = nullptr; - fml::AutoResetWaitableEvent latch2; - fml::AutoResetWaitableEvent term2; - std::thread thread2([&loop2, &latch2, &term2]() { - fml::MessageLoop::EnsureInitializedForCurrentThread(); - loop2 = &fml::MessageLoop::GetCurrent(); - latch2.Signal(); - term2.Wait(); - }); - - latch1.Wait(); - latch2.Wait(); - - fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId(); - fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId(); + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); const auto raster_thread_merger = fml::MakeRefCounted(qid1, qid2); @@ -616,11 +487,153 @@ TEST(RasterThreadMerger, SetMergeUnmergeCallback) { raster_thread_merger->DecrementLease(); ASSERT_EQ(2, callbacks); +} - term1.Signal(); - term2.Signal(); - thread1.join(); - thread2.join(); +TEST(RasterThreadMerger, MultipleMergersCanMergeSameThreadPair) { + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + // Two mergers will share one same inner merger + const auto raster_thread_merger1 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2); + const auto raster_thread_merger2 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(raster_thread_merger1, + qid1, qid2); + const size_t kNumFramesMerged = 5; + ASSERT_FALSE(raster_thread_merger1->IsMerged()); + ASSERT_FALSE(raster_thread_merger2->IsMerged()); + + // Merge using the first merger + raster_thread_merger1->MergeWithLease(kNumFramesMerged); + + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + + // let there be one more turn till the leases expire. + for (size_t i = 0; i < kNumFramesMerged - 1; i++) { + // Check merge state using the two merger + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + raster_thread_merger1->DecrementLease(); + } + + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + + // extend the lease once with the first merger + raster_thread_merger1->ExtendLeaseTo(kNumFramesMerged); + + // we will NOT last for 1 extra turn, we just set it. + for (size_t i = 0; i < kNumFramesMerged; i++) { + // Check merge state using the two merger + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + raster_thread_merger1->DecrementLease(); + } + + ASSERT_FALSE(raster_thread_merger1->IsMerged()); + ASSERT_FALSE(raster_thread_merger2->IsMerged()); +} + +TEST(RasterThreadMerger, TheLastCallerOfMultipleMergersCanUnmergeNow) { + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + // Two mergers will share one same inner merger + const auto raster_thread_merger1 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2); + const auto raster_thread_merger2 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(raster_thread_merger1, + qid1, qid2); + const size_t kNumFramesMerged = 5; + ASSERT_FALSE(raster_thread_merger1->IsMerged()); + ASSERT_FALSE(raster_thread_merger2->IsMerged()); + + // Merge using the mergers + raster_thread_merger1->MergeWithLease(kNumFramesMerged); + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + // Extend the second merger's lease + raster_thread_merger2->ExtendLeaseTo(kNumFramesMerged); + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + + // Two callers state becomes one caller left. + raster_thread_merger1->UnMergeNowIfLastOne(); + // Check if still merged + ASSERT_TRUE(raster_thread_merger1->IsMerged()); + ASSERT_TRUE(raster_thread_merger2->IsMerged()); + + // One caller state becomes no callers left. + raster_thread_merger2->UnMergeNowIfLastOne(); + // Check if unmerged + ASSERT_FALSE(raster_thread_merger1->IsMerged()); + ASSERT_FALSE(raster_thread_merger2->IsMerged()); +} + +/// This case tests multiple standalone engines using independent merger to +/// merge two different raster threads into the same platform thread. +TEST(RasterThreadMerger, + TwoIndependentMergersCanMergeTwoDifferentThreadsIntoSamePlatformThread) { + TaskQueueWrapper queue1; + TaskQueueWrapper queue2; + TaskQueueWrapper queue3; + fml::TaskQueueId qid1 = queue1.GetTaskQueueId(); + fml::TaskQueueId qid2 = queue2.GetTaskQueueId(); + fml::TaskQueueId qid3 = queue3.GetTaskQueueId(); + + // Two mergers will NOT share same inner merger + const auto merger_from_2_to_1 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2); + const auto merger_from_3_to_1 = + fml::RasterThreadMerger::CreateOrShareThreadMerger(merger_from_2_to_1, + qid1, qid3); + const size_t kNumFramesMerged = 5; + ASSERT_FALSE(merger_from_2_to_1->IsMerged()); + ASSERT_FALSE(merger_from_3_to_1->IsMerged()); + + // Merge thread2 into thread1 + merger_from_2_to_1->MergeWithLease(kNumFramesMerged); + // Merge thread3 into thread1 + merger_from_3_to_1->MergeWithLease(kNumFramesMerged); + + ASSERT_TRUE(merger_from_2_to_1->IsMerged()); + ASSERT_TRUE(merger_from_3_to_1->IsMerged()); + + for (size_t i = 0; i < kNumFramesMerged; i++) { + ASSERT_TRUE(merger_from_2_to_1->IsMerged()); + merger_from_2_to_1->DecrementLease(); + } + + ASSERT_FALSE(merger_from_2_to_1->IsMerged()); + ASSERT_TRUE(merger_from_3_to_1->IsMerged()); + + for (size_t i = 0; i < kNumFramesMerged; i++) { + ASSERT_TRUE(merger_from_3_to_1->IsMerged()); + merger_from_3_to_1->DecrementLease(); + } + + ASSERT_FALSE(merger_from_2_to_1->IsMerged()); + ASSERT_FALSE(merger_from_3_to_1->IsMerged()); + + merger_from_2_to_1->MergeWithLease(kNumFramesMerged); + ASSERT_TRUE(merger_from_2_to_1->IsMerged()); + ASSERT_FALSE(merger_from_3_to_1->IsMerged()); + merger_from_3_to_1->MergeWithLease(kNumFramesMerged); + ASSERT_TRUE(merger_from_2_to_1->IsMerged()); + ASSERT_TRUE(merger_from_3_to_1->IsMerged()); + + // Can unmerge independently + merger_from_2_to_1->UnMergeNowIfLastOne(); + ASSERT_FALSE(merger_from_2_to_1->IsMerged()); + ASSERT_TRUE(merger_from_3_to_1->IsMerged()); + + // Can unmerge independently + merger_from_3_to_1->UnMergeNowIfLastOne(); + ASSERT_FALSE(merger_from_2_to_1->IsMerged()); + ASSERT_FALSE(merger_from_3_to_1->IsMerged()); } } // namespace testing diff --git a/fml/shared_thread_merger.cc b/fml/shared_thread_merger.cc new file mode 100644 index 0000000000000..3ee0eb69eeca1 --- /dev/null +++ b/fml/shared_thread_merger.cc @@ -0,0 +1,93 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/fml/shared_thread_merger.h" + +#include + +namespace fml { + +SharedThreadMerger::SharedThreadMerger(fml::TaskQueueId owner, + fml::TaskQueueId subsumed) + : owner_(owner), + subsumed_(subsumed), + task_queues_(fml::MessageLoopTaskQueues::GetInstance()) {} + +bool SharedThreadMerger::MergeWithLease(RasterThreadMergerId caller, + size_t lease_term) { + FML_DCHECK(lease_term > 0) << "lease_term should be positive."; + std::scoped_lock lock(mutex_); + if (IsMergedUnSafe()) { + return true; + } + bool success = task_queues_->Merge(owner_, subsumed_); + FML_CHECK(success) << "Unable to merge the raster and platform threads."; + // Save the lease term + lease_term_by_caller_[caller] = lease_term; + return success; +} + +bool SharedThreadMerger::UnMergeNowUnSafe() { + FML_CHECK(IsAllLeaseTermsZeroUnSafe()) + << "all lease term records must be zero before calling " + "UnMergeNowUnSafe()"; + bool success = task_queues_->Unmerge(owner_, subsumed_); + FML_CHECK(success) << "Unable to un-merge the raster and platform threads."; + return success; +} + +bool SharedThreadMerger::UnMergeNowIfLastOne(RasterThreadMergerId caller) { + std::scoped_lock lock(mutex_); + lease_term_by_caller_.erase(caller); + if (!lease_term_by_caller_.empty()) { + return true; + } + return UnMergeNowUnSafe(); +} + +bool SharedThreadMerger::DecrementLease(RasterThreadMergerId caller) { + std::scoped_lock lock(mutex_); + auto entry = lease_term_by_caller_.find(caller); + bool exist = entry != lease_term_by_caller_.end(); + if (exist) { + std::atomic_size_t& lease_term_ref = entry->second; + FML_CHECK(lease_term_ref > 0) + << "lease_term should always be positive when merged, lease_term=" + << lease_term_ref; + lease_term_ref--; + } else { + FML_LOG(WARNING) << "The caller does not exist when calling " + "DecrementLease(), ignored. This may happens after " + "caller is erased in UnMergeNowIfLastOne(). caller=" + << caller; + } + if (IsAllLeaseTermsZeroUnSafe()) { + // Unmerge now because lease_term_ decreased to zero. + UnMergeNowUnSafe(); + return true; + } + return false; +} + +void SharedThreadMerger::ExtendLeaseTo(RasterThreadMergerId caller, + size_t lease_term) { + FML_DCHECK(lease_term > 0) << "lease_term should be positive."; + std::scoped_lock lock(mutex_); + FML_DCHECK(IsMergedUnSafe()) + << "should be merged state when calling this method"; + lease_term_by_caller_[caller] = lease_term; +} + +bool SharedThreadMerger::IsMergedUnSafe() const { + return !IsAllLeaseTermsZeroUnSafe(); +} + +bool SharedThreadMerger::IsAllLeaseTermsZeroUnSafe() const { + return std::all_of(lease_term_by_caller_.begin(), lease_term_by_caller_.end(), + [&](const auto& item) { return item.second == 0; }); +} + +} // namespace fml diff --git a/fml/shared_thread_merger.h b/fml/shared_thread_merger.h new file mode 100644 index 0000000000000..30b3d88e1be96 --- /dev/null +++ b/fml/shared_thread_merger.h @@ -0,0 +1,69 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_SHARED_THREAD_MERGER_H_ +#define FLUTTER_FML_SHARED_THREAD_MERGER_H_ + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/memory/ref_counted.h" +#include "flutter/fml/message_loop_task_queues.h" + +namespace fml { + +class RasterThreadMerger; + +typedef void* RasterThreadMergerId; + +/// Instance of this class is shared between multiple |RasterThreadMerger| +/// instances, Most of the callings from |RasterThreadMerger| will be redirected +/// to this class with one more caller parameter. +class SharedThreadMerger + : public fml::RefCountedThreadSafe { + public: + SharedThreadMerger(TaskQueueId owner, TaskQueueId subsumed); + + // It's called by |RasterThreadMerger::MergeWithLease|. + // See the doc of |RasterThreadMerger::MergeWithLease|. + bool MergeWithLease(RasterThreadMergerId caller, size_t lease_term); + + // It's called by |RasterThreadMerger::UnMergeNowIfLastOne|. + // See the doc of |RasterThreadMerger::UnMergeNowIfLastOne|. + bool UnMergeNowIfLastOne(RasterThreadMergerId caller); + + // It's called by |RasterThreadMerger::ExtendLeaseTo|. + // See the doc of |RasterThreadMerger::ExtendLeaseTo|. + void ExtendLeaseTo(RasterThreadMergerId caller, size_t lease_term); + + // It's called by |RasterThreadMerger::IsMergedUnSafe|. + // See the doc of |RasterThreadMerger::IsMergedUnSafe|. + bool IsMergedUnSafe() const; + + // It's called by |RasterThreadMerger::DecrementLease|. + // See the doc of |RasterThreadMerger::DecrementLease|. + bool DecrementLease(RasterThreadMergerId caller); + + private: + fml::TaskQueueId owner_; + fml::TaskQueueId subsumed_; + fml::RefPtr task_queues_; + std::mutex mutex_; + + /// The |MergeWithLease| or |ExtendLeaseTo| method will record the caller + /// into this lease_term_by_caller_ map, |UnMergeNowIfLastOne| + /// method will remove the caller from this lease_term_by_caller_. + std::map lease_term_by_caller_; + + bool IsAllLeaseTermsZeroUnSafe() const; + + bool UnMergeNowUnSafe(); + + FML_DISALLOW_COPY_AND_ASSIGN(SharedThreadMerger); +}; + +} // namespace fml + +#endif // FLUTTER_FML_SHARED_THREAD_MERGER_H_ diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 89f47ace61dc1..502e04c4416ae 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -68,8 +68,8 @@ void Rasterizer::Setup(std::unique_ptr surface) { delegate_.GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId(); const auto gpu_id = delegate_.GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(); - raster_thread_merger_ = - fml::MakeRefCounted(platform_id, gpu_id); + raster_thread_merger_ = fml::RasterThreadMerger::CreateOrShareThreadMerger( + delegate_.GetParentRasterThreadMerger(), platform_id, gpu_id); } if (raster_thread_merger_) { raster_thread_merger_->SetMergeUnmergeCallback([=]() { @@ -94,7 +94,7 @@ void Rasterizer::Teardown() { if (raster_thread_merger_.get() != nullptr && raster_thread_merger_.get()->IsMerged()) { FML_DCHECK(raster_thread_merger_->IsEnabled()); - raster_thread_merger_->UnMergeNow(); + raster_thread_merger_->UnMergeNowIfLastOne(); raster_thread_merger_->SetMergeUnmergeCallback(nullptr); } } @@ -503,24 +503,12 @@ RasterStatus Rasterizer::DrawToSurface( frame->supports_readback(), // surface supports pixel reads raster_thread_merger_ // thread merger ); - if (compositor_frame) { RasterStatus raster_status = compositor_frame->Raster(layer_tree, false); if (raster_status == RasterStatus::kFailed || raster_status == RasterStatus::kSkipAndRetry) { return raster_status; } - if (shared_engine_block_thread_merging_ && raster_thread_merger_ && - raster_thread_merger_->IsMerged()) { - // TODO(73620): Remove when platform views are accounted for. - FML_LOG(ERROR) - << "Error: Thread merging not implemented for engines with shared " - "components.\n\n" - "This is likely a result of using platform views with enigne " - "groups. See " - "https://github.com/flutter/flutter/issues/73620."; - fml::KillProcess(); - } if (external_view_embedder_ && (!raster_thread_merger_ || raster_thread_merger_->IsMerged())) { FML_DCHECK(!frame->IsSubmitted()); @@ -718,6 +706,10 @@ void Rasterizer::SetSnapshotSurfaceProducer( snapshot_surface_producer_ = std::move(producer); } +fml::RefPtr Rasterizer::GetRasterThreadMerger() { + return raster_thread_merger_; +} + void Rasterizer::FireNextFrameCallbackIfPresent() { if (!next_frame_callback_) { return; diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index 311428802d386..15beadf5511a5 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -84,6 +84,10 @@ class Rasterizer final : public SnapshotDelegate { /// Task runners used by the shell. virtual const TaskRunners& GetTaskRunners() const = 0; + /// The raster thread merger from parent shell's rasterizer. + virtual const fml::RefPtr + GetParentRasterThreadMerger() const = 0; + /// Accessor for the shell's GPU sync switch, which determines whether GPU /// operations are allowed on the current thread. /// @@ -371,6 +375,14 @@ class Rasterizer final : public SnapshotDelegate { return compositor_context_.get(); } + //---------------------------------------------------------------------------- + /// @brief Returns the raster thread merger used by this rasterizer. + /// This may be `nullptr`. + /// + /// @return The raster thread merger used by this rasterizer. + /// + fml::RefPtr GetRasterThreadMerger(); + //---------------------------------------------------------------------------- /// @brief Skia has no notion of time. To work around the performance /// implications of this, it may cache GPU resources to reference @@ -434,15 +446,6 @@ class Rasterizer final : public SnapshotDelegate { /// void DisableThreadMergerIfNeeded(); - /// @brief Mechanism to stop thread merging when using shared engine - /// components. - /// @details This is a temporary workaround until thread merging can be - /// supported correctly. This should be called on the raster - /// thread. - /// @see https://github.com/flutter/flutter/issues/73620 - /// - void BlockThreadMerging() { shared_engine_block_thread_merging_ = true; } - private: Delegate& delegate_; std::unique_ptr surface_; @@ -460,8 +463,6 @@ class Rasterizer final : public SnapshotDelegate { fml::RefPtr raster_thread_merger_; fml::TaskRunnerAffineWeakPtrFactory weak_factory_; std::shared_ptr external_view_embedder_; - bool shared_engine_block_thread_merging_ = false; - // |SnapshotDelegate| sk_sp MakeRasterSnapshot( std::function draw_callback, diff --git a/shell/common/rasterizer_unittests.cc b/shell/common/rasterizer_unittests.cc index cb54fef27a097..3bd3f82465285 100644 --- a/shell/common/rasterizer_unittests.cc +++ b/shell/common/rasterizer_unittests.cc @@ -28,6 +28,8 @@ class MockDelegate : public Rasterizer::Delegate { MOCK_METHOD0(GetFrameBudget, fml::Milliseconds()); MOCK_CONST_METHOD0(GetLatestFrameTargetTime, fml::TimePoint()); MOCK_CONST_METHOD0(GetTaskRunners, const TaskRunners&()); + MOCK_CONST_METHOD0(GetParentRasterThreadMerger, + const fml::RefPtr()); MOCK_CONST_METHOD0(GetIsGpuDisabledSyncSwitch, std::shared_ptr()); MOCK_METHOD0(CreateSnapshotSurface, std::unique_ptr()); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 47f0b5bb3db74..edc4b6e021e3e 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -149,6 +149,7 @@ std::unique_ptr Shell::Create( } return CreateWithSnapshot(std::move(platform_data), // std::move(task_runners), // + /*parent_merger=*/nullptr, // std::move(settings), // std::move(vm), // std::move(isolate_snapshot), // @@ -159,6 +160,7 @@ std::unique_ptr Shell::Create( std::unique_ptr Shell::CreateShellOnPlatformThread( DartVMRef vm, + fml::RefPtr parent_merger, TaskRunners task_runners, const PlatformData& platform_data, Settings settings, @@ -173,7 +175,7 @@ std::unique_ptr Shell::CreateShellOnPlatformThread( } auto shell = std::unique_ptr( - new Shell(std::move(vm), task_runners, settings, + new Shell(std::move(vm), task_runners, parent_merger, settings, std::make_shared( task_runners.GetUITaskRunner(), !settings.skia_deterministic_rendering_on_cpu), @@ -301,6 +303,7 @@ std::unique_ptr Shell::CreateShellOnPlatformThread( std::unique_ptr Shell::CreateWithSnapshot( const PlatformData& platform_data, TaskRunners task_runners, + fml::RefPtr parent_thread_merger, Settings settings, DartVMRef vm, fml::RefPtr isolate_snapshot, @@ -326,6 +329,7 @@ std::unique_ptr Shell::CreateWithSnapshot( fml::MakeCopyable( [&latch, // &shell, // + parent_thread_merger, // task_runners = std::move(task_runners), // platform_data = std::move(platform_data), // settings = std::move(settings), // @@ -337,6 +341,7 @@ std::unique_ptr Shell::CreateWithSnapshot( is_gpu_disabled]() mutable { shell = CreateShellOnPlatformThread( std::move(vm), // + parent_thread_merger, // std::move(task_runners), // std::move(platform_data), // std::move(settings), // @@ -352,10 +357,12 @@ std::unique_ptr Shell::CreateWithSnapshot( Shell::Shell(DartVMRef vm, TaskRunners task_runners, + fml::RefPtr parent_merger, Settings settings, std::shared_ptr volatile_path_tracker, bool is_gpu_disabled) : task_runners_(std::move(task_runners)), + parent_raster_thread_merger_(parent_merger), settings_(std::move(settings)), vm_(std::move(vm)), is_gpu_disabled_sync_switch_(new fml::SyncSwitch(is_gpu_disabled)), @@ -477,9 +484,9 @@ std::unique_ptr Shell::Spawn( FML_DCHECK(task_runners_.IsValid()); auto shell_maker = [&](bool is_gpu_disabled) { std::unique_ptr result(CreateWithSnapshot( - PlatformData{}, task_runners_, GetSettings(), vm_, - vm_->GetVMData()->GetIsolateSnapshot(), on_create_platform_view, - on_create_rasterizer, + PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(), + GetSettings(), vm_, vm_->GetVMData()->GetIsolateSnapshot(), + on_create_platform_view, on_create_rasterizer, [engine = this->engine_.get()]( Engine::Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm, @@ -505,18 +512,6 @@ std::unique_ptr Shell::Spawn( .SetIfTrue([&] { result = shell_maker(true); })); result->shared_resource_context_ = io_manager_->GetSharedResourceContext(); result->RunEngine(std::move(run_configuration)); - - task_runners_.GetRasterTaskRunner()->PostTask( - [rasterizer = rasterizer_->GetWeakPtr(), - spawn_rasterizer = result->rasterizer_->GetWeakPtr()]() { - if (rasterizer) { - rasterizer->BlockThreadMerging(); - } - if (spawn_rasterizer) { - spawn_rasterizer->BlockThreadMerging(); - } - }); - return result; } @@ -674,6 +669,11 @@ const TaskRunners& Shell::GetTaskRunners() const { return task_runners_; } +const fml::RefPtr Shell::GetParentRasterThreadMerger() + const { + return parent_raster_thread_merger_; +} + fml::TaskRunnerAffineWeakPtr Shell::GetRasterizer() const { FML_DCHECK(is_setup_); return weak_rasterizer_; diff --git a/shell/common/shell.h b/shell/common/shell.h index 0ad74798717cc..23754936c4e81 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -229,6 +229,16 @@ class Shell final : public PlatformView::Delegate, /// const TaskRunners& GetTaskRunners() const override; + //------------------------------------------------------------------------------ + /// @brief Getting the raster thread merger from parent shell, it can be + /// a null RefPtr when it's a root Shell or the + /// embedder_->SupportsDynamicThreadMerging() returns false. + /// + /// @return The raster thread merger used by the parent shell. + /// + const fml::RefPtr GetParentRasterThreadMerger() + const override; + //---------------------------------------------------------------------------- /// @brief Rasterizers may only be accessed on the raster task runner. /// @@ -390,6 +400,7 @@ class Shell final : public PlatformView::Delegate, rapidjson::Document*)>; const TaskRunners task_runners_; + const fml::RefPtr parent_raster_thread_merger_; const Settings settings_; DartVMRef vm_; mutable std::mutex time_recorder_mutex_; @@ -453,12 +464,14 @@ class Shell final : public PlatformView::Delegate, Shell(DartVMRef vm, TaskRunners task_runners, + fml::RefPtr parent_merger, Settings settings, std::shared_ptr volatile_path_tracker, bool is_gpu_disabled); static std::unique_ptr CreateShellOnPlatformThread( DartVMRef vm, + fml::RefPtr parent_merger, TaskRunners task_runners, const PlatformData& platform_data, Settings settings, @@ -470,6 +483,7 @@ class Shell final : public PlatformView::Delegate, static std::unique_ptr CreateWithSnapshot( const PlatformData& platform_data, TaskRunners task_runners, + fml::RefPtr parent_thread_merger, Settings settings, DartVMRef vm, fml::RefPtr isolate_snapshot, diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index ced4ccc790906..d083b66d45bd7 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -816,7 +816,7 @@ TEST_F(ShellTest, OnPlatformViewDestroyAfterMergingThreads #endif ) { - const size_t ThreadMergingLease = 10; + const int ThreadMergingLease = 10; auto settings = CreateSettingsForFixture(); fml::AutoResetWaitableEvent end_frame_latch; std::shared_ptr external_view_embedder; @@ -896,7 +896,7 @@ TEST_F(ShellTest, OnPlatformViewDestroyWhenThreadsAreMerging #endif ) { - const size_t kThreadMergingLease = 10; + const int kThreadMergingLease = 10; auto settings = CreateSettingsForFixture(); fml::AutoResetWaitableEvent end_frame_latch; auto end_frame_callback =