From 019710806a309cf9a2cbe66ca75b775fd46bbde6 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 5 Nov 2024 17:10:48 -0800 Subject: [PATCH] Do not stop flutter_tester if microtasks are still pending Flutter_tester has a task observer that checks whether the test's Dart code has finished execution. If Dart no longer has live ports but does have pending microtasks, then flutter_tester should continue running and force a drain of the microtask queue. Fixes https://github.com/flutter/flutter/issues/158129 --- lib/ui/ui_dart_state.cc | 4 ++++ lib/ui/ui_dart_state.h | 2 ++ runtime/runtime_controller.cc | 8 ++++++++ runtime/runtime_controller.h | 9 +++++++++ shell/common/engine.cc | 4 ++++ shell/common/engine.h | 9 +++++++++ shell/common/shell.cc | 11 +++++++++++ shell/common/shell.h | 11 +++++++++++ shell/testing/tester_main.cc | 10 ++++++++++ 9 files changed, 68 insertions(+) diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index 7440a88b24d93..f4022b04afb4e 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -169,6 +169,10 @@ void UIDartState::FlushMicrotasksNow() { microtask_queue_.RunMicrotasks(); } +bool UIDartState::HasPendingMicrotasks() { + return microtask_queue_.HasMicrotasks(); +} + void UIDartState::AddOrRemoveTaskObserver(bool add) { auto task_runner = context_.task_runners.GetUITaskRunner(); if (!task_runner) { diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h index 3ce014f44fa75..514d1df3e4d87 100644 --- a/lib/ui/ui_dart_state.h +++ b/lib/ui/ui_dart_state.h @@ -130,6 +130,8 @@ class UIDartState : public tonic::DartState { void FlushMicrotasksNow(); + bool HasPendingMicrotasks(); + fml::WeakPtr GetIOManager() const; fml::RefPtr GetSkiaUnrefQueue() const; diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 51613d6430ada..ece3b90242584 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -511,6 +511,14 @@ bool RuntimeController::HasLivePorts() { return Dart_HasLivePorts(); } +bool RuntimeController::HasPendingMicrotasks() { + std::shared_ptr root_isolate = root_isolate_.lock(); + if (!root_isolate) { + return false; + } + return root_isolate->HasPendingMicrotasks(); +} + tonic::DartErrorHandleType RuntimeController::GetLastError() { std::shared_ptr root_isolate = root_isolate_.lock(); return root_isolate ? root_isolate->GetLastError() : tonic::kNoError; diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index c19f856644caf..b90bcac0e7ac6 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -523,6 +523,15 @@ class RuntimeController : public PlatformConfigurationClient, /// bool HasLivePorts(); + //---------------------------------------------------------------------------- + /// @brief Returns if the root isolate has any pending microtasks. + /// + /// @return True if there are microtasks that have been queued but not + /// run, False otherwise. Return False if the root isolate is not + /// running as well. + /// + bool HasPendingMicrotasks(); + //---------------------------------------------------------------------------- /// @brief Get the last error encountered by the microtask queue. /// diff --git a/shell/common/engine.cc b/shell/common/engine.cc index e524230fb8122..9269ce38d3888 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -293,6 +293,10 @@ bool Engine::UIIsolateHasLivePorts() { return runtime_controller_->HasLivePorts(); } +bool Engine::UIIsolateHasPendingMicrotasks() { + return runtime_controller_->HasPendingMicrotasks(); +} + tonic::DartErrorHandleType Engine::GetUIIsolateLastError() { return runtime_controller_->GetLastError(); } diff --git a/shell/common/engine.h b/shell/common/engine.h index 2530b7cd2949f..4f4823cbeb4a1 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -682,6 +682,15 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// bool UIIsolateHasLivePorts(); + /// @brief Another signal of liveness is the presence of microtasks that + /// have been queued by the application but have not yet been + /// executed. Embedders may want to check for pending microtasks + /// and ensure that the microtask queue has been drained before + /// the embedder terminates. + /// + /// @return Check if the root isolate has any pending microtasks. + bool UIIsolateHasPendingMicrotasks(); + //---------------------------------------------------------------------------- /// @brief Errors that are unhandled on the Dart message loop are kept /// for further inspection till the next unhandled error comes diff --git a/shell/common/shell.cc b/shell/common/shell.cc index e634941db1660..60779793529a2 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -710,6 +710,17 @@ bool Shell::EngineHasLivePorts() const { return weak_engine_->UIIsolateHasLivePorts(); } +bool Shell::EngineHasPendingMicrotasks() const { + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + if (!weak_engine_) { + return false; + } + + return weak_engine_->UIIsolateHasPendingMicrotasks(); +} + bool Shell::IsSetup() const { return is_set_up_; } diff --git a/shell/common/shell.h b/shell/common/shell.h index 0032403deaf69..0685bb7a9b5f2 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -358,6 +358,17 @@ class Shell final : public PlatformView::Delegate, /// bool EngineHasLivePorts() const; + //---------------------------------------------------------------------------- + /// @brief Used by embedders to check if the Engine is running and has + /// any microtasks that have been queued but have not yet run. + /// The Flutter tester uses this as a signal that a test is still + /// running. + /// + /// @return Returns if the shell has an engine and the engine has pending + /// microtasks. + /// + bool EngineHasPendingMicrotasks() const; + //---------------------------------------------------------------------------- /// @brief Accessor for the disable GPU SyncSwitch. // |Rasterizer::Delegate| diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 75516fbcb13a9..c29fe2e6097fd 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -267,9 +267,11 @@ class ScriptCompletionTaskObserver { public: ScriptCompletionTaskObserver(Shell& shell, fml::RefPtr main_task_runner, + fml::RefPtr ui_task_runner, bool run_forever) : shell_(shell), main_task_runner_(std::move(main_task_runner)), + ui_task_runner_(std::move(ui_task_runner)), run_forever_(run_forever) {} int GetExitCodeForLastError() const { @@ -283,6 +285,12 @@ class ScriptCompletionTaskObserver { // just yet. return; } + if (shell_.EngineHasPendingMicrotasks()) { + // Post an empty task to force a run of the engine task observer that + // drains the microtask queue. + ui_task_runner_->PostTask([] {}); + return; + } if (run_forever_) { // We need this script to run forever. We have already recorded the last @@ -302,6 +310,7 @@ class ScriptCompletionTaskObserver { private: Shell& shell_; fml::RefPtr main_task_runner_; + fml::RefPtr ui_task_runner_; bool run_forever_ = false; std::optional last_error_; bool has_terminated_ = false; @@ -456,6 +465,7 @@ int RunTester(const flutter::Settings& settings, *shell, // a valid shell fml::MessageLoop::GetCurrent() .GetTaskRunner(), // the message loop to terminate + ui_task_runner, // runner for Dart microtasks run_forever // should the exit be ignored );