| // 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. | |
| #include "flutter/runtime/dart_vm.h" | |
| #include "flutter/runtime/dart_vm_lifecycle.h" | |
| #include "flutter/runtime/platform_isolate_manager.h" | |
| #include "flutter/testing/fixture_test.h" | |
| #include "flutter/testing/testing.h" | |
| namespace flutter { | |
| namespace testing { | |
| struct IsolateData { | |
| PlatformIsolateManager* mgr; | |
| Dart_Isolate isolate = nullptr; | |
| bool is_shutdown = false; | |
| bool was_registered = false; | |
| explicit IsolateData(PlatformIsolateManager* _mgr) : mgr(_mgr) {} | |
| }; | |
| // The IsolateDataMap is a map from Dart_Isolate to a *vector* of IsolateData, | |
| // because Dart_Isolates are frequently reused after shutdown, and we want the | |
| // IsolateData objects to live as long as the map itself. The last element of | |
| // the vector is always the currently active IsolateData, and the other elements | |
| // refer to isolates that have been shutdown. | |
| using IsolateDataMap = | |
| std::unordered_map<Dart_Isolate, std::vector<std::unique_ptr<IsolateData>>>; | |
| // Using a thread local isolate data map so that MultithreadedCreation test | |
| // can avoid using locks while creating isolates on multiple threads. A lock | |
| // would sync up the threads, so would defeat the purpose of the test. | |
| static thread_local std::unique_ptr<IsolateDataMap> isolate_data_map_; | |
| class PlatformIsolateManagerTest : public FixtureTest { | |
| public: | |
| PlatformIsolateManagerTest() {} | |
| void TestWithRootIsolate(const std::function<void()>& test) { | |
| ASSERT_FALSE(DartVMRef::IsInstanceRunning()); | |
| auto settings = CreateSettingsForFixture(); | |
| auto vm_ref = DartVMRef::Create(settings); | |
| ASSERT_TRUE(vm_ref); | |
| auto vm_data = vm_ref.GetVMData(); | |
| ASSERT_TRUE(vm_data); | |
| TaskRunners task_runners(GetCurrentTestName(), // | |
| GetCurrentTaskRunner(), // | |
| GetCurrentTaskRunner(), // | |
| GetCurrentTaskRunner(), // | |
| GetCurrentTaskRunner() // | |
| ); | |
| auto isolate_configuration = | |
| IsolateConfiguration::InferFromSettings(settings); | |
| UIDartState::Context context(task_runners); | |
| context.advisory_script_uri = "main.dart"; | |
| context.advisory_script_entrypoint = "main"; | |
| auto weak_isolate = DartIsolate::CreateRunningRootIsolate( | |
| vm_data->GetSettings(), // settings | |
| vm_data->GetIsolateSnapshot(), // isolate snapshot | |
| nullptr, // platform configuration | |
| DartIsolate::Flags{}, // flags | |
| nullptr, // root_isolate_create_callback | |
| settings.isolate_create_callback, // isolate create callback | |
| settings.isolate_shutdown_callback, // isolate shutdown callback | |
| "main", // dart entrypoint | |
| std::nullopt, // dart entrypoint library | |
| {}, // dart entrypoint arguments | |
| std::move(isolate_configuration), // isolate configuration | |
| context // engine context | |
| ); | |
| root_isolate_ = weak_isolate.lock()->isolate(); | |
| ASSERT_TRUE(root_isolate_); | |
| test(); | |
| Dart_EnterIsolate(root_isolate_); | |
| Dart_ShutdownIsolate(); | |
| } | |
| Dart_Isolate CreateAndRegisterIsolate(PlatformIsolateManager* mgr) { | |
| if (isolate_data_map_.get() == nullptr) { | |
| isolate_data_map_.reset(new IsolateDataMap()); | |
| } | |
| IsolateData* isolate_data = new IsolateData(mgr); | |
| char* error = nullptr; | |
| Dart_Isolate isolate = | |
| Dart_CreateIsolateInGroup(root_isolate_, "TestIsolate", OnShutdown, | |
| nullptr, isolate_data, &error); | |
| isolate_data->isolate = isolate; | |
| EXPECT_TRUE(isolate); | |
| Dart_ExitIsolate(); | |
| (*isolate_data_map_.get())[isolate].push_back( | |
| std::unique_ptr<IsolateData>(isolate_data)); | |
| isolate_data->was_registered = mgr->RegisterPlatformIsolate(isolate); | |
| return isolate; | |
| } | |
| bool IsolateIsShutdown(Dart_Isolate isolate) { | |
| EXPECT_EQ(1u, isolate_data_map_.get()->count(isolate)); | |
| EXPECT_LT(0u, (*isolate_data_map_.get())[isolate].size()); | |
| return (*isolate_data_map_.get())[isolate].back()->is_shutdown; | |
| } | |
| bool IsolateWasRegistered(Dart_Isolate isolate) { | |
| EXPECT_EQ(1u, isolate_data_map_.get()->count(isolate)); | |
| EXPECT_LT(0u, (*isolate_data_map_.get())[isolate].size()); | |
| return (*isolate_data_map_.get())[isolate].back()->was_registered; | |
| } | |
| private: | |
| Dart_Isolate root_isolate_ = nullptr; | |
| static void OnShutdown(void*, void* raw_isolate_data) { | |
| IsolateData* isolate_data = | |
| reinterpret_cast<IsolateData*>(raw_isolate_data); | |
| EXPECT_TRUE(isolate_data->isolate); | |
| EXPECT_FALSE(isolate_data->is_shutdown); | |
| isolate_data->is_shutdown = true; | |
| if (isolate_data->was_registered) { | |
| isolate_data->mgr->RemovePlatformIsolate(isolate_data->isolate); | |
| } | |
| } | |
| FML_DISALLOW_COPY_AND_ASSIGN(PlatformIsolateManagerTest); | |
| }; | |
| TEST_F(PlatformIsolateManagerTest, OrdinaryFlow) { | |
| TestWithRootIsolate([this]() { | |
| PlatformIsolateManager mgr; | |
| EXPECT_FALSE(mgr.HasShutdown()); | |
| EXPECT_FALSE(mgr.HasShutdownMaybeFalseNegative()); | |
| Dart_Isolate isolateA = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateA); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateA)); | |
| EXPECT_TRUE(IsolateWasRegistered(isolateA)); | |
| EXPECT_TRUE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| Dart_Isolate isolateB = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateB); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateB)); | |
| EXPECT_TRUE(IsolateWasRegistered(isolateB)); | |
| EXPECT_TRUE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| mgr.ShutdownPlatformIsolates(); | |
| EXPECT_TRUE(mgr.HasShutdown()); | |
| EXPECT_TRUE(mgr.HasShutdownMaybeFalseNegative()); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateA)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateB)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| }); | |
| } | |
| TEST_F(PlatformIsolateManagerTest, EarlyShutdown) { | |
| TestWithRootIsolate([this]() { | |
| PlatformIsolateManager mgr; | |
| EXPECT_FALSE(mgr.HasShutdown()); | |
| Dart_Isolate isolateA = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateA); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateA)); | |
| EXPECT_TRUE(IsolateWasRegistered(isolateA)); | |
| EXPECT_TRUE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| Dart_Isolate isolateB = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateB); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateB)); | |
| EXPECT_TRUE(IsolateWasRegistered(isolateB)); | |
| EXPECT_TRUE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| Dart_EnterIsolate(isolateA); | |
| Dart_ShutdownIsolate(); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateA)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| Dart_EnterIsolate(isolateB); | |
| Dart_ShutdownIsolate(); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateB)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| mgr.ShutdownPlatformIsolates(); | |
| EXPECT_TRUE(mgr.HasShutdown()); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateA)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateB)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| }); | |
| } | |
| TEST_F(PlatformIsolateManagerTest, RegistrationAfterShutdown) { | |
| TestWithRootIsolate([this]() { | |
| PlatformIsolateManager mgr; | |
| EXPECT_FALSE(mgr.HasShutdown()); | |
| Dart_Isolate isolateA = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateA); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateA)); | |
| EXPECT_TRUE(IsolateWasRegistered(isolateA)); | |
| EXPECT_TRUE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| mgr.ShutdownPlatformIsolates(); | |
| EXPECT_TRUE(mgr.HasShutdown()); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateA)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateA)); | |
| Dart_Isolate isolateB = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolateB); | |
| EXPECT_FALSE(IsolateIsShutdown(isolateB)); | |
| EXPECT_FALSE(IsolateWasRegistered(isolateB)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| Dart_EnterIsolate(isolateB); | |
| Dart_ShutdownIsolate(); | |
| EXPECT_TRUE(IsolateIsShutdown(isolateB)); | |
| EXPECT_FALSE(mgr.IsRegisteredForTestingOnly(isolateB)); | |
| }); | |
| } | |
| TEST_F(PlatformIsolateManagerTest, MultithreadedCreation) { | |
| // Try to generate race conditions by creating Isolates on multiple threads, | |
| // while shutting down the manager. | |
| TestWithRootIsolate([this]() { | |
| PlatformIsolateManager mgr; | |
| EXPECT_FALSE(mgr.HasShutdown()); | |
| std::atomic<bool> test_finished = false; | |
| std::vector<std::thread> threads; | |
| threads.reserve(10); | |
| for (int i = 0; i < 10; ++i) { | |
| threads.push_back(std::thread([this, &mgr, &test_finished]() { | |
| for (int j = 0; j < 100; ++j) { | |
| Dart_Isolate isolate = CreateAndRegisterIsolate(&mgr); | |
| ASSERT_TRUE(isolate); | |
| if (!IsolateWasRegistered(isolate)) { | |
| Dart_EnterIsolate(isolate); | |
| Dart_ShutdownIsolate(); | |
| } | |
| } | |
| while (!test_finished.load()) { | |
| // Wait for the test to finish, to avoid prematurely destroying thread | |
| // local isolate_data_map_. | |
| } | |
| })); | |
| } | |
| mgr.ShutdownPlatformIsolates(); | |
| EXPECT_TRUE(mgr.HasShutdown()); | |
| test_finished = true; | |
| for (auto& thread : threads) { | |
| thread.join(); | |
| } | |
| }); | |
| } | |
| } // namespace testing | |
| } // namespace flutter |