| // Copyright 2025 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "base/files/file_enumerator.h" | |
| #include "base/files/file_path_watcher.h" | |
| #include "base/files/file_util.h" | |
| #include "base/run_loop.h" | |
| #include "base/task/task_traits.h" | |
| #include "base/task/thread_pool.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "base/threading/thread_restrictions.h" | |
| #include "components/viz/common/features.h" | |
| #include "components/viz/host/persistent_cache_sandboxed_file_factory.h" | |
| #include "components/viz/test/gpu_host_impl_test_api.h" | |
| #include "components/viz/test/stub_gpu_service.h" | |
| #include "content/browser/gpu/gpu_process_host.h" | |
| #include "content/public/test/browser_test.h" | |
| #include "content/public/test/content_browser_test.h" | |
| #include "gpu/config/gpu_feature_info.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "media/media_buildflags.h" | |
| #include "mojo/public/cpp/bindings/receiver.h" | |
| #include "services/viz/privileged/mojom/gl/gpu_service.mojom.h" | |
| #include "skia/buildflags.h" | |
| namespace content { | |
| namespace { | |
| // Test implementation of viz::mojom::GpuService which only implements the | |
| // persistent cache aspects. | |
| class TestGpuService : public viz::StubGpuService { | |
| public: | |
| explicit TestGpuService(base::RepeatingClosure quit_closure) | |
| : quit_closure_(quit_closure) {} | |
| TestGpuService(const TestGpuService*) = delete; | |
| ~TestGpuService() override = default; | |
| TestGpuService& operator=(const TestGpuService&) = delete; | |
| // mojom::GpuService: | |
| void SetChannelPersistentCachePendingBackend( | |
| int32_t client_id, | |
| const gpu::GpuDiskCacheHandle& handle, | |
| persistent_cache::PendingBackend pending_backend) override { | |
| quit_closure_.Run(); | |
| } | |
| private: | |
| base::RepeatingClosure quit_closure_; | |
| }; | |
| class TestCacheFileFactory : public viz::PersistentCacheSandboxedFileFactory { | |
| public: | |
| TestCacheFileFactory( | |
| const base::FilePath& cache_root_dir, | |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner) | |
| : viz::PersistentCacheSandboxedFileFactory( | |
| cache_root_dir, | |
| std::move(background_task_runner)) {} | |
| private: | |
| ~TestCacheFileFactory() override = default; | |
| }; | |
| } // namespace | |
| // Test GpuHostImpl's behaviors when PersistentCache is used for shader caching. | |
| class GpuHostImplPersistentCacheTest : public ContentBrowserTest { | |
| public: | |
| GpuHostImplPersistentCacheTest() | |
| : gpu_service_thread_("Gpu Service"), | |
| cache_factory_thread_("Cache Factory") { | |
| feature_list_.InitWithFeatures( | |
| {features::kSkiaGraphite, features::kSkiaGraphiteUsePersistentCache}, | |
| {}); | |
| CHECK(temp_dir_.CreateUniqueTempDir()); | |
| cache_factory_thread_.Start(); | |
| cache_factory_ = base::MakeRefCounted<TestCacheFileFactory>( | |
| temp_dir_.GetPath(), cache_factory_thread_.task_runner()); | |
| viz::PersistentCacheSandboxedFileFactory::SetInstanceForTesting( | |
| cache_factory_.get()); | |
| } | |
| ~GpuHostImplPersistentCacheTest() override { | |
| base::ScopedAllowBlockingForTesting allow_blocking; | |
| viz::PersistentCacheSandboxedFileFactory::SetInstanceForTesting(nullptr); | |
| cache_factory_thread_.Stop(); | |
| } | |
| void PreRunTestOnMainThread() override { | |
| gpu_service_thread_.StartAndWaitForTesting(); | |
| run_loop_for_cache_file_wait_ = std::make_unique<base::RunLoop>(); | |
| gpu_host_impl_test_api_ = std::make_unique<viz::GpuHostImplTestApi>( | |
| GpuProcessHost::Get()->gpu_host()); | |
| mojo::Remote<viz::mojom::GpuService> gpu_service_remote; | |
| auto receiver = gpu_service_remote.BindNewPipeAndPassReceiver(); | |
| gpu_host_impl_test_api_->SetGpuService(std::move(gpu_service_remote)); | |
| PostTaskToGpuServiceThreadAndWait( | |
| base::BindOnce(&GpuHostImplPersistentCacheTest::InitOnGpuServiceThread, | |
| base::Unretained(this), std::move(receiver), | |
| run_loop_for_cache_file_wait_->QuitClosure())); | |
| ContentBrowserTest::PreRunTestOnMainThread(); | |
| } | |
| void PostRunTestOnMainThread() override { | |
| PostTaskToGpuServiceThreadAndWait( | |
| base::BindOnce([](GpuServiceThreadState gpu_service_thread_state) {}, | |
| std::move(gpu_service_thread_state_))); | |
| gpu_service_thread_.Stop(); | |
| ContentBrowserTest::PostRunTestOnMainThread(); | |
| } | |
| void WaitForSetChannelPersistentCacheFile() { | |
| run_loop_for_cache_file_wait_->Run(); | |
| } | |
| protected: | |
| void InitOnGpuServiceThread( | |
| mojo::PendingReceiver<viz::mojom::GpuService> receiver, | |
| base::RepeatingClosure quit_closure) { | |
| gpu_service_thread_state_.test_gpu_service = | |
| std::make_unique<TestGpuService>(std::move(quit_closure)); | |
| gpu_service_thread_state_.gpu_service_receiver = | |
| std::make_unique<mojo::Receiver<viz::mojom::GpuService>>( | |
| gpu_service_thread_state_.test_gpu_service.get(), | |
| std::move(receiver)); | |
| } | |
| void PostTaskToGpuServiceThreadAndWait(base::OnceClosure task) { | |
| base::WaitableEvent completion_event; | |
| gpu_service_thread_.task_runner()->PostTask( | |
| FROM_HERE, | |
| base::BindOnce( | |
| [](base::OnceClosure task, base::WaitableEvent* wait_event) { | |
| std::move(task).Run(); | |
| wait_event->Signal(); | |
| }, | |
| std::move(task), &completion_event)); | |
| completion_event.Wait(); | |
| } | |
| struct GpuServiceThreadState { | |
| std::unique_ptr<TestGpuService> test_gpu_service; | |
| std::unique_ptr<mojo::Receiver<viz::mojom::GpuService>> | |
| gpu_service_receiver; | |
| }; | |
| base::test::ScopedFeatureList feature_list_; | |
| base::Thread gpu_service_thread_; | |
| base::Thread cache_factory_thread_; | |
| // RunLoop for waiting on the main thread for SetChannelPersistentCacheFile | |
| // to trigger. | |
| std::unique_ptr<base::RunLoop> run_loop_for_cache_file_wait_; | |
| std::unique_ptr<viz::GpuHostImplTestApi> gpu_host_impl_test_api_; | |
| GpuServiceThreadState gpu_service_thread_state_; | |
| base::ScopedTempDir temp_dir_; | |
| scoped_refptr<TestCacheFileFactory> cache_factory_; | |
| }; | |
| #if BUILDFLAG(SKIA_USE_DAWN) | |
| // Check that the cache files exist after SetChannelPersistentCacheFile is | |
| // called. | |
| IN_PROC_BROWSER_TEST_F(GpuHostImplPersistentCacheTest, | |
| SetChannelPersistentCacheFileCalled) { | |
| base::ScopedAllowBlockingForTesting allow_blocking; | |
| WaitForSetChannelPersistentCacheFile(); | |
| EXPECT_TRUE(base::PathExists(temp_dir_.GetPath())); | |
| EXPECT_FALSE(base::IsDirectoryEmpty(temp_dir_.GetPath())); | |
| } | |
| // Check that the cache files are cleared after a crash. | |
| IN_PROC_BROWSER_TEST_F(GpuHostImplPersistentCacheTest, ClearCacheOnCrash) { | |
| base::ScopedAllowBlockingForTesting allow_blocking; | |
| WaitForSetChannelPersistentCacheFile(); | |
| base::RunLoop run_loop; | |
| std::vector<std::unique_ptr<base::FilePathWatcher>> watchers; | |
| // Get the cache directories, they should be the only child folders in the | |
| // temp dir. | |
| base::FileEnumerator enumerator(temp_dir_.GetPath(), false, | |
| base::FileEnumerator::DIRECTORIES); | |
| std::set<base::FilePath> cache_dirs; | |
| for (base::FilePath cache_dir = enumerator.Next(); !cache_dir.empty(); | |
| cache_dir = enumerator.Next()) { | |
| // Verify that the cache dir is not empty. | |
| EXPECT_FALSE(base::IsDirectoryEmpty(cache_dir)); | |
| cache_dirs.insert(cache_dir); | |
| // Watch for cache directory changes | |
| auto watcher = std::make_unique<base::FilePathWatcher>(); | |
| watcher->Watch( | |
| cache_dir, base::FilePathWatcher::Type::kNonRecursive, | |
| base::BindRepeating( | |
| [](base::RunLoop* run_loop, const base::FilePath& cache_dir, | |
| std::set<base::FilePath>* cache_dirs, const base::FilePath& path, | |
| bool error) { | |
| if (base::IsDirectoryEmpty(cache_dir)) { | |
| cache_dirs->erase(cache_dir); | |
| // End the run loop if all cache directories are cleared. | |
| if (cache_dirs->empty()) { | |
| run_loop->Quit(); | |
| } | |
| } | |
| }, | |
| &run_loop, cache_dir, &cache_dirs)); | |
| watchers.push_back(std::move(watcher)); | |
| } | |
| EXPECT_FALSE(cache_dirs.empty()); | |
| // Simulate a crash. | |
| viz::GpuHostImpl* gpu_host_impl = GpuProcessHost::Get()->gpu_host(); | |
| gpu::GpuProcessShmCount service_count( | |
| gpu_host_impl->GetShaderCacheShmCountForTesting()->CloneRegion()); | |
| gpu::GpuProcessShmCount::ScopedIncrement scoped_increment(&service_count); | |
| gpu_host_impl->OnProcessCrashed(); | |
| // The cache directories should be empty after the crash. | |
| run_loop.Run(); | |
| // The file watcher callback removes empty entries from cache_dirs, verify | |
| // they are all cleared. | |
| EXPECT_TRUE(cache_dirs.empty()); | |
| } | |
| #endif // BUILDFLAG(SKIA_USE_DAWN) | |
| } // namespace content |