// Copyright 2018 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/threading/platform_thread_win.h" #include #include #include "base/process/process.h" #include "base/test/scoped_feature_list.h" #include "base/threading/platform_thread_win.h" #include "base/threading/simple_thread.h" #include "base/threading/threading_features.h" #include "base/win/windows_version.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { // It has been observed that calling // :SetThreadPriority(THREAD_MODE_BACKGROUND_BEGIN) in an IDLE_PRIORITY_CLASS // process never affects the return value of ::GetThreadPriority() or // the base priority reported in Process Explorer. It does however // set the memory and I/O priorities to very low. This test confirms that // behavior which we suspect is a Windows kernel bug. If this test starts // failing, the mitigation for https://crbug.com/901483 in // PlatformThread::SetCurrentThreadType() should be revisited. TEST(PlatformThreadWinTest, SetBackgroundThreadModeFailsInIdlePriorityProcess) { PlatformThreadHandle::Handle thread_handle = PlatformThread::CurrentHandle().platform_handle(); // ::GetThreadPriority() is NORMAL. Memory priority is NORMAL. // Note: There is no practical way to verify the I/O priority. EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL); // Set the process priority to IDLE. // Note: Do not use Process::SetPriority() because it uses // PROCESS_MODE_BACKGROUND_BEGIN instead of IDLE_PRIORITY_CLASS when // the target is the current process. EXPECT_EQ(::GetPriorityClass(Process::Current().Handle()), static_cast(NORMAL_PRIORITY_CLASS)); ::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS); EXPECT_EQ(Process::Current().GetOSPriority(), static_cast(IDLE_PRIORITY_CLASS)); // GetThreadPriority() stays NORMAL. Memory priority stays NORMAL. EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL); // Begin thread mode background. EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_BEGIN)); // On Win10+, GetThreadPriority() stays NORMAL and memory priority becomes // VERY_LOW. // // Note: this documents the aforementioned kernel bug. Ideally this would // *not* be the case. const int priority_after_thread_mode_background_begin = ::GetThreadPriority(thread_handle); EXPECT_EQ(priority_after_thread_mode_background_begin, THREAD_PRIORITY_NORMAL); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW); PlatformThread::Sleep(base::Seconds(1)); // After 1 second, GetThreadPriority() and memory priority don't change (this // refutes the hypothesis that it simply takes time before GetThreadPriority() // is updated after entering thread mode background). EXPECT_EQ(::GetThreadPriority(thread_handle), priority_after_thread_mode_background_begin); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW); // Set the process priority to NORMAL. ::SetPriorityClass(Process::Current().Handle(), NORMAL_PRIORITY_CLASS); // GetThreadPriority() and memory priority don't change when the process // priority changes. EXPECT_EQ(::GetThreadPriority(thread_handle), priority_after_thread_mode_background_begin); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW); // End thread mode background. // // Note: at least "ending" the semi-enforced background mode works... EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END)); // GetThreadPriority() stays/becomes NORMAL. Memory priority becomes NORMAL. EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL); internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL); } namespace { class MemoryPriorityAssertingThreadDelegate : public base::PlatformThread::Delegate { public: explicit MemoryPriorityAssertingThreadDelegate(LONG memory_priority) : memory_priority_(memory_priority) {} void ThreadMain() override { PlatformThreadHandle::Handle thread_handle = PlatformThread::CurrentHandle().platform_handle(); internal::AssertMemoryPriority(thread_handle, memory_priority_); } LONG memory_priority_; }; } // namespace // It has been observed (crbug.com/1489467) that memory priority is set to very // low on background threads, and a possible mitigation is running in the // kThreadNormalMemoryPriorityWin experiment which sets memory priority to // NORMAL on all threads at creation. If this test fails, the feature is broken // and investigation needs to be done into whether pages are being allocated at // pri-1 despite it as shown in the above linked bug. TEST(PlatformThreadWinTest, NormalPriorityFeatureForBackgroundThreads) { base::test::ScopedFeatureList list; list.InitAndEnableFeature(kBackgroundThreadNormalMemoryPriorityWin); base::InitializePlatformThreadFeatures(); MemoryPriorityAssertingThreadDelegate delegate{MEMORY_PRIORITY_NORMAL}; PlatformThreadHandle handle; CHECK(PlatformThread::CreateWithType(0, &delegate, &handle, ThreadType::kBackground)); PlatformThread::Join(handle); } TEST(PlatformThreadWinTest, BackgroundThreadsSetLowMemoryPriority) { base::InitializePlatformThreadFeatures(); MemoryPriorityAssertingThreadDelegate delegate{MEMORY_PRIORITY_VERY_LOW}; PlatformThreadHandle handle; CHECK(PlatformThread::CreateWithType(0, &delegate, &handle, ThreadType::kBackground)); PlatformThread::Join(handle); } } // namespace base