Create base::ScopedBoostPriority
This patch provides with a base::ScopedBoostPriority class for
temporarily boosting thread priority. The original thread priority
will be set back again when the object is destructed.
This fixes a bug where the priority was boosted even when the platform
was unable to undo the operation. This might as a side-effect keep
threads at NORMAL priority that were previously unintentionally
permanently boosted to DISPLAY priority.
Fixed: 1335489
Change-Id: I6a58f5b4c5a7dace86bd7788f6ceca7ecb9baeb8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3699224
Reviewed-by: Gabriel Charette <gab@chromium.org>
Commit-Queue: Zhibo Wang <zhibo1.wang@intel.com>
Cr-Commit-Position: refs/heads/main@{#1013320}
diff --git a/AUTHORS b/AUTHORS
index a68e49b..30f3f22 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1328,6 +1328,7 @@
Zhengkun Li <zhengkli@amazon.com>
Zhenyu Liang <zhenyu.liang@intel.com>
Zhenyu Shan <zhenyu.shan@intel.com>
+Zhibo Wang <zhibo1.wang@intel.com>
Zhifei Fang <facetothefate@gmail.com>
Zhiyuan Ye <zhiyuanye@tencent.com>
Zhuoyu Qian <zhuoyu.qian@samsung.com>
diff --git a/base/files/important_file_writer.cc b/base/files/important_file_writer.cc
index 7018d4c..eb9c54f 100644
--- a/base/files/important_file_writer.cc
+++ b/base/files/important_file_writer.cc
@@ -31,6 +31,7 @@
#include "base/task/task_runner.h"
#include "base/task/task_runner_util.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/scoped_thread_priority.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
@@ -212,6 +213,7 @@
}
File::Error replace_file_error = File::FILE_OK;
+ bool result;
// The file must be closed for ReplaceFile to do its job, which opens up a
// race with other software that may open the temp file (e.g., an A/V scanner
@@ -219,28 +221,24 @@
// Windows and close as late as possible to improve the chances that the other
// software will lose the race.
#if BUILDFLAG(IS_WIN)
- const auto previous_priority = PlatformThread::GetCurrentThreadPriority();
- const bool reset_priority = previous_priority <= ThreadPriority::NORMAL;
- if (reset_priority)
- PlatformThread::SetCurrentThreadPriority(ThreadPriority::DISPLAY);
-#endif // BUILDFLAG(IS_WIN)
- tmp_file.Close();
- bool result = ReplaceFile(tmp_file_path, path, &replace_file_error);
-#if BUILDFLAG(IS_WIN)
- // Save and restore the last error code so that it's not polluted by the
- // thread priority change.
- auto last_error = ::GetLastError();
+ DWORD last_error;
int retry_count = 0;
- for (/**/; !result && retry_count < kReplaceRetries; ++retry_count) {
- // The race condition between closing the temporary file and moving it gets
- // hit on a regular basis on some systems (https://crbug.com/1099284), so
- // we retry a few times before giving up.
- PlatformThread::Sleep(kReplacePauseInterval);
+ {
+ ScopedBoostPriority scoped_boost_priority(ThreadPriority::DISPLAY);
+ tmp_file.Close();
result = ReplaceFile(tmp_file_path, path, &replace_file_error);
+ // Save and restore the last error code so that it's not polluted by the
+ // thread priority change.
last_error = ::GetLastError();
+ for (/**/; !result && retry_count < kReplaceRetries; ++retry_count) {
+ // The race condition between closing the temporary file and moving it
+ // gets hit on a regular basis on some systems
+ // (https://crbug.com/1099284), so we retry a few times before giving up.
+ PlatformThread::Sleep(kReplacePauseInterval);
+ result = ReplaceFile(tmp_file_path, path, &replace_file_error);
+ last_error = ::GetLastError();
+ }
}
- if (reset_priority)
- PlatformThread::SetCurrentThreadPriority(previous_priority);
// Log how many times we had to retry the ReplaceFile operation before it
// succeeded. If we never succeeded then return a special value.
@@ -248,6 +246,9 @@
retry_count = kReplaceRetryFailure;
UmaHistogramExactLinear("ImportantFile.FileReplaceRetryCount", retry_count,
kReplaceRetryFailure);
+#else
+ tmp_file.Close();
+ result = ReplaceFile(tmp_file_path, path, &replace_file_error);
#endif // BUILDFLAG(IS_WIN)
if (!result) {
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index 72f2737..a249519 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -85,6 +85,7 @@
DISPLAY,
// Suitable for low-latency, glitch-resistant audio.
REALTIME_AUDIO,
+ kMaxValue = REALTIME_AUDIO,
};
// A namespace for low-level thread functions.
diff --git a/base/threading/scoped_thread_priority.cc b/base/threading/scoped_thread_priority.cc
index 8a27505..edd89c3b 100644
--- a/base/threading/scoped_thread_priority.cc
+++ b/base/threading/scoped_thread_priority.cc
@@ -10,6 +10,27 @@
#include "build/build_config.h"
namespace base {
+
+ScopedBoostPriority::ScopedBoostPriority(ThreadPriority target_priority) {
+ DCHECK_LT(target_priority, ThreadPriority::REALTIME_AUDIO);
+ const ThreadPriority original_priority =
+ PlatformThread::GetCurrentThreadPriority();
+ const bool should_boost = original_priority < target_priority &&
+ PlatformThread::CanChangeThreadPriority(
+ original_priority, target_priority) &&
+ PlatformThread::CanChangeThreadPriority(
+ target_priority, original_priority);
+ if (should_boost) {
+ original_priority_.emplace(original_priority);
+ PlatformThread::SetCurrentThreadPriority(target_priority);
+ }
+}
+
+ScopedBoostPriority::~ScopedBoostPriority() {
+ if (original_priority_.has_value())
+ PlatformThread::SetCurrentThreadPriority(original_priority_.value());
+}
+
namespace internal {
ScopedMayLoadLibraryAtBackgroundPriority::
diff --git a/base/threading/scoped_thread_priority.h b/base/threading/scoped_thread_priority.h
index 4720d7c..dfb8bdec 100644
--- a/base/threading/scoped_thread_priority.h
+++ b/base/threading/scoped_thread_priority.h
@@ -63,6 +63,20 @@
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
scoped_may_load_library_at_background_priority)(FROM_HERE, nullptr);
+// Boosts the current thread's priority to match the priority of threads of
+// |target_thread_type| in this scope.
+class BASE_EXPORT ScopedBoostPriority {
+ public:
+ explicit ScopedBoostPriority(ThreadPriority target_priority);
+ ~ScopedBoostPriority();
+
+ ScopedBoostPriority(const ScopedBoostPriority&) = delete;
+ ScopedBoostPriority& operator=(const ScopedBoostPriority&) = delete;
+
+ private:
+ absl::optional<ThreadPriority> original_priority_;
+};
+
namespace internal {
class BASE_EXPORT ScopedMayLoadLibraryAtBackgroundPriority {
diff --git a/base/threading/scoped_thread_priority_unittest.cc b/base/threading/scoped_thread_priority_unittest.cc
index 4bc8169..6973349 100644
--- a/base/threading/scoped_thread_priority_unittest.cc
+++ b/base/threading/scoped_thread_priority_unittest.cc
@@ -5,6 +5,7 @@
#include "base/threading/scoped_thread_priority.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -21,6 +22,15 @@
ADD_FAILURE() << "This test cannot run multiple times in the same " \
"process.";
+static ThreadPriority kAllThreadTypes[] = {
+ ThreadPriority::REALTIME_AUDIO, ThreadPriority::DISPLAY,
+ ThreadPriority::NORMAL, ThreadPriority::BACKGROUND};
+
+static_assert(static_cast<int>(ThreadPriority::BACKGROUND) == 0,
+ "kBackground isn't lowest");
+static_assert(ThreadPriority::REALTIME_AUDIO == ThreadPriority::kMaxValue,
+ "kRealtimeAudio isn't highest");
+
class ScopedThreadPriorityTest : public testing::Test {
protected:
void SetUp() override {
@@ -46,6 +56,41 @@
} // namespace
+TEST_F(ScopedThreadPriorityTest, BasicTest) {
+ for (auto from : kAllThreadTypes) {
+ if (!PlatformThread::CanChangeThreadPriority(ThreadPriority::NORMAL, from))
+ continue;
+ for (auto to : kAllThreadTypes) {
+ // ThreadType::kRealtimeAudio is not a valid |target_thread_type| for
+ // ScopedBoostPriority.
+ if (to == ThreadPriority::REALTIME_AUDIO)
+ continue;
+ Thread::Options options;
+ options.priority = from;
+ Thread thread("ScopedThreadPriorityTest");
+ thread.StartWithOptions(std::move(options));
+ thread.WaitUntilThreadStarted();
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](ThreadPriority from, ThreadPriority to) {
+ EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), from);
+ {
+ ScopedBoostPriority scoped_boost_priority(to);
+ bool will_boost_priority =
+ from < to &&
+ PlatformThread::CanChangeThreadPriority(from, to) &&
+ PlatformThread::CanChangeThreadPriority(to, from);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(),
+ will_boost_priority ? to : from);
+ }
+ EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), from);
+ },
+ from, to));
+ }
+ }
+}
+
TEST_F(ScopedThreadPriorityTest, WithoutPriorityBoost) {
ASSERT_RUNS_ONCE();
diff --git a/components/startup_metric_utils/browser/startup_metric_utils.cc b/components/startup_metric_utils/browser/startup_metric_utils.cc
index 4f4c0f8..6eb6f9f 100644
--- a/components/startup_metric_utils/browser/startup_metric_utils.cc
+++ b/components/startup_metric_utils/browser/startup_metric_utils.cc
@@ -21,6 +21,7 @@
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/scoped_thread_priority.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
@@ -300,35 +301,26 @@
// base::TimeTicks::Now() at play, but in practice it is pretty much instant
// compared to multi-seconds startup timings.
base::TimeTicks StartupTimeToTimeTicks(base::Time time) {
-// First get a base which represents the same point in time in both units.
-// Bump the priority of this thread while doing this as the wall clock time it
-// takes to resolve these two calls affects the precision of this method and
-// bumping the priority reduces the likelihood of a context switch interfering
-// with this computation.
+ // First get a base which represents the same point in time in both units.
+ // Bump the priority of this thread while doing this as the wall clock time it
+ // takes to resolve these two calls affects the precision of this method and
+ // bumping the priority reduces the likelihood of a context switch interfering
+ // with this computation.
+ absl::optional<base::ScopedBoostPriority> scoped_boost_priority;
// Enabling this logic on OS X causes a significant performance regression.
// https://crbug.com/601270
#if !BUILDFLAG(IS_APPLE)
static bool statics_initialized = false;
-
- base::ThreadPriority previous_priority = base::ThreadPriority::NORMAL;
if (!statics_initialized) {
- previous_priority = base::PlatformThread::GetCurrentThreadPriority();
- base::PlatformThread::SetCurrentThreadPriority(
- base::ThreadPriority::DISPLAY);
+ statics_initialized = true;
+ scoped_boost_priority.emplace(base::ThreadPriority::DISPLAY);
}
-#endif
+#endif // !BUILDFLAG(IS_APPLE)
static const base::Time time_base = base::Time::Now();
static const base::TimeTicks trace_ticks_base = base::TimeTicks::Now();
-#if !BUILDFLAG(IS_APPLE)
- if (!statics_initialized) {
- base::PlatformThread::SetCurrentThreadPriority(previous_priority);
- }
- statics_initialized = true;
-#endif
-
// Then use the TimeDelta common ground between the two units to make the
// conversion.
const base::TimeDelta delta_since_base = time_base - time;