1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/threading/platform_thread_win.h"
6
7 #include <windows.h>
8
9 #include <array>
10
11 #include "base/process/process.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "base/threading/platform_thread_win.h"
14 #include "base/threading/simple_thread.h"
15 #include "base/threading/threading_features.h"
16 #include "base/win/windows_version.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 namespace base {
21
22 // It has been observed that calling
23 // :SetThreadPriority(THREAD_MODE_BACKGROUND_BEGIN) in an IDLE_PRIORITY_CLASS
24 // process never affects the return value of ::GetThreadPriority() or
25 // the base priority reported in Process Explorer. It does however
26 // set the memory and I/O priorities to very low. This test confirms that
27 // behavior which we suspect is a Windows kernel bug. If this test starts
28 // failing, the mitigation for https://crbug.com/901483 in
29 // PlatformThread::SetCurrentThreadType() should be revisited.
TEST(PlatformThreadWinTest,SetBackgroundThreadModeFailsInIdlePriorityProcess)30 TEST(PlatformThreadWinTest, SetBackgroundThreadModeFailsInIdlePriorityProcess) {
31 PlatformThreadHandle::Handle thread_handle =
32 PlatformThread::CurrentHandle().platform_handle();
33
34 // ::GetThreadPriority() is NORMAL. Memory priority is NORMAL.
35 // Note: There is no practical way to verify the I/O priority.
36 EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
37 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
38
39 // Set the process priority to IDLE.
40 // Note: Do not use Process::SetPriority() because it uses
41 // PROCESS_MODE_BACKGROUND_BEGIN instead of IDLE_PRIORITY_CLASS when
42 // the target is the current process.
43 EXPECT_EQ(::GetPriorityClass(Process::Current().Handle()),
44 static_cast<DWORD>(NORMAL_PRIORITY_CLASS));
45 ::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS);
46 EXPECT_EQ(Process::Current().GetOSPriority(),
47 static_cast<int>(IDLE_PRIORITY_CLASS));
48
49 // GetThreadPriority() stays NORMAL. Memory priority stays NORMAL.
50 EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
51 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
52
53 // Begin thread mode background.
54 EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_BEGIN));
55
56 // On Win10+, GetThreadPriority() stays NORMAL and memory priority becomes
57 // VERY_LOW.
58 //
59 // Note: this documents the aforementioned kernel bug. Ideally this would
60 // *not* be the case.
61 const int priority_after_thread_mode_background_begin =
62 ::GetThreadPriority(thread_handle);
63 EXPECT_EQ(priority_after_thread_mode_background_begin,
64 THREAD_PRIORITY_NORMAL);
65 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
66
67 PlatformThread::Sleep(base::Seconds(1));
68
69 // After 1 second, GetThreadPriority() and memory priority don't change (this
70 // refutes the hypothesis that it simply takes time before GetThreadPriority()
71 // is updated after entering thread mode background).
72 EXPECT_EQ(::GetThreadPriority(thread_handle),
73 priority_after_thread_mode_background_begin);
74 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
75
76 // Set the process priority to NORMAL.
77 ::SetPriorityClass(Process::Current().Handle(), NORMAL_PRIORITY_CLASS);
78
79 // GetThreadPriority() and memory priority don't change when the process
80 // priority changes.
81 EXPECT_EQ(::GetThreadPriority(thread_handle),
82 priority_after_thread_mode_background_begin);
83 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
84
85 // End thread mode background.
86 //
87 // Note: at least "ending" the semi-enforced background mode works...
88 EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END));
89
90 // GetThreadPriority() stays/becomes NORMAL. Memory priority becomes NORMAL.
91 EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
92 internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
93 }
94
95 namespace {
96 class MemoryPriorityAssertingThreadDelegate
97 : public base::PlatformThread::Delegate {
98 public:
MemoryPriorityAssertingThreadDelegate(LONG memory_priority)99 explicit MemoryPriorityAssertingThreadDelegate(LONG memory_priority)
100 : memory_priority_(memory_priority) {}
101
ThreadMain()102 void ThreadMain() override {
103 PlatformThreadHandle::Handle thread_handle =
104 PlatformThread::CurrentHandle().platform_handle();
105 internal::AssertMemoryPriority(thread_handle, memory_priority_);
106 }
107
108 LONG memory_priority_;
109 };
110 } // namespace
111
112 // It has been observed (crbug.com/1489467) that memory priority is set to very
113 // low on background threads, and a possible mitigation is running in the
114 // kThreadNormalMemoryPriorityWin experiment which sets memory priority to
115 // NORMAL on all threads at creation. If this test fails, the feature is broken
116 // and investigation needs to be done into whether pages are being allocated at
117 // pri-1 despite it as shown in the above linked bug.
TEST(PlatformThreadWinTest,NormalPriorityFeatureForBackgroundThreads)118 TEST(PlatformThreadWinTest, NormalPriorityFeatureForBackgroundThreads) {
119 base::test::ScopedFeatureList list;
120 list.InitAndEnableFeature(kBackgroundThreadNormalMemoryPriorityWin);
121 base::InitializePlatformThreadFeatures();
122
123 MemoryPriorityAssertingThreadDelegate delegate{MEMORY_PRIORITY_NORMAL};
124
125 PlatformThreadHandle handle;
126
127 CHECK(PlatformThread::CreateWithType(0, &delegate, &handle,
128 ThreadType::kBackground));
129 PlatformThread::Join(handle);
130 }
131
TEST(PlatformThreadWinTest,BackgroundThreadsSetLowMemoryPriority)132 TEST(PlatformThreadWinTest, BackgroundThreadsSetLowMemoryPriority) {
133 base::InitializePlatformThreadFeatures();
134
135 MemoryPriorityAssertingThreadDelegate delegate{MEMORY_PRIORITY_VERY_LOW};
136
137 PlatformThreadHandle handle;
138
139 CHECK(PlatformThread::CreateWithType(0, &delegate, &handle,
140 ThreadType::kBackground));
141 PlatformThread::Join(handle);
142 }
143
144 } // namespace base
145