diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 553c8f5c5e092..aaf279610562c 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -235,6 +235,19 @@ bool RuntimeController::NotifyIdle(fml::TimePoint deadline) { return true; } +bool RuntimeController::NotifyDestroyed() { + std::shared_ptr root_isolate = root_isolate_.lock(); + if (!root_isolate) { + return false; + } + + tonic::DartState::Scope scope(root_isolate); + + Dart_NotifyDestroyed(); + + return true; +} + bool RuntimeController::DispatchPlatformMessage( std::unique_ptr message) { if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 20b0d17c4c649..f6c50fbdd8c80 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -360,6 +360,17 @@ class RuntimeController : public PlatformConfigurationClient { /// virtual bool NotifyIdle(fml::TimePoint deadline); + //---------------------------------------------------------------------------- + /// @brief Notify the Dart VM that the attached flutter view has been + /// destroyed. This gives the Dart VM to perform some cleanup + /// activities e.g: perform garbage collection to free up any + /// unused memory. + /// + /// NotifyDestroyed is advisory. The VM may or may not perform any clean up + /// activities. + /// + virtual bool NotifyDestroyed(); + //---------------------------------------------------------------------------- /// @brief Returns if the root isolate is running. The isolate must be /// transitioned to the running phase manually. The isolate can diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 8f19bdc9e0402..85db9043eeff7 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -266,6 +266,11 @@ void Engine::NotifyIdle(fml::TimePoint deadline) { runtime_controller_->NotifyIdle(deadline); } +void Engine::NotifyDestroyed() { + TRACE_EVENT0("flutter", "Engine::NotifyDestroyed"); + runtime_controller_->NotifyDestroyed(); +} + std::optional Engine::GetUIIsolateReturnCode() { return runtime_controller_->GetRootIsolateReturnCode(); } diff --git a/shell/common/engine.h b/shell/common/engine.h index 62f09fd79fbde..82e041e6ff2d1 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -554,6 +554,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// void NotifyIdle(fml::TimePoint deadline); + //---------------------------------------------------------------------------- + /// @brief Notifies the engine that the attached flutter view has been + /// destroyed. + /// This enables the engine to notify the Dart VM so it can do + /// some cleanp activities. + void NotifyDestroyed(); + //---------------------------------------------------------------------------- /// @brief Dart code cannot fully measure the time it takes for a /// specific frame to be rendered. This is because Dart code only diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 9a7c02179fd6b..605e3d951d230 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -212,6 +212,11 @@ void performanceModeImpactsNotifyIdle() { PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.balanced); } +@pragma('vm:entry-point') +void callNotifyDestroyed() { + notifyDestroyed(); +} + @pragma('vm:external-name', 'NotifyMessage') external void notifyMessage(String string); @@ -424,6 +429,8 @@ Future runCallback(IsolateParam param) async { @pragma('vm:entry-point') @pragma('vm:external-name', 'NotifyNativeBool') external void notifyNativeBool(bool value); +@pragma('vm:external-name', 'NotifyDestroyed') +external void notifyDestroyed(); @pragma('vm:entry-point') Future testPluginUtilitiesCallbackHandle() async { diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 20ca64b8578e9..a8bad9ea960b4 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -851,6 +851,14 @@ void Shell::OnPlatformViewDestroyed() { // This incorrect assumption can lead to deadlock. rasterizer_->DisableThreadMergerIfNeeded(); + // Notify the Dart VM that the PlatformView has been destroyed and some + // cleanup activity can be done (e.g: garbage collect the Dart heap). + task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() { + if (engine) { + engine->NotifyDestroyed(); + } + }); + // Note: // This is a synchronous operation because certain platforms depend on // setup/suspension of all activities that may be interacting with the GPU in diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 91f2a24864902..c40ca2af356c3 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -3932,6 +3932,40 @@ TEST_F(ShellTest, NotifyIdleNotCalledInLatencyMode) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +TEST_F(ShellTest, NotifyDestroyed) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::Platform | ThreadHost::UI | + ThreadHost::IO | ThreadHost::RASTER); + auto platform_task_runner = thread_host.platform_thread->GetTaskRunner(); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + auto shell = CreateShell(settings, task_runners); + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + ASSERT_TRUE(ValidateShell(shell.get())); + + fml::CountDownLatch latch(1); + AddNativeCallback("NotifyDestroyed", CREATE_NATIVE_ENTRY([&](auto args) { + auto runtime_controller = const_cast( + shell->GetEngine()->GetRuntimeController()); + bool success = runtime_controller->NotifyDestroyed(); + EXPECT_TRUE(success); + latch.CountDown(); + })); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("callNotifyDestroyed"); + RunEngine(shell.get(), std::move(configuration)); + + latch.Wait(); + + DestroyShell(std::move(shell), task_runners); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + } // namespace testing } // namespace flutter