Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Do not stop flutter_tester if microtasks are still pending #56432

Merged
merged 1 commit into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/ui/ui_dart_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions lib/ui/ui_dart_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ class UIDartState : public tonic::DartState {

void FlushMicrotasksNow();

bool HasPendingMicrotasks();

fml::WeakPtr<IOManager> GetIOManager() const;

fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const;
Expand Down
8 changes: 8 additions & 0 deletions runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,14 @@ bool RuntimeController::HasLivePorts() {
return Dart_HasLivePorts();
}

bool RuntimeController::HasPendingMicrotasks() {
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
if (!root_isolate) {
return false;
}
return root_isolate->HasPendingMicrotasks();
}

tonic::DartErrorHandleType RuntimeController::GetLastError() {
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
return root_isolate ? root_isolate->GetLastError() : tonic::kNoError;
Expand Down
9 changes: 9 additions & 0 deletions runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
4 changes: 4 additions & 0 deletions shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
9 changes: 9 additions & 0 deletions shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}
Expand Down
11 changes: 11 additions & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
10 changes: 10 additions & 0 deletions shell/testing/tester_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,11 @@ class ScriptCompletionTaskObserver {
public:
ScriptCompletionTaskObserver(Shell& shell,
fml::RefPtr<fml::TaskRunner> main_task_runner,
fml::RefPtr<fml::TaskRunner> 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 {
Expand All @@ -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
Expand All @@ -302,6 +310,7 @@ class ScriptCompletionTaskObserver {
private:
Shell& shell_;
fml::RefPtr<fml::TaskRunner> main_task_runner_;
fml::RefPtr<fml::TaskRunner> ui_task_runner_;
bool run_forever_ = false;
std::optional<DartErrorCode> last_error_;
bool has_terminated_ = false;
Expand Down Expand Up @@ -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
);

Expand Down