// 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 <windows.h>
#include <array>
#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<DWORD>(NORMAL_PRIORITY_CLASS));
::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS);
EXPECT_EQ(Process::Current().GetOSPriority(),
static_cast<int>(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
} // namespace base