Commit 90d3de3c authored by Etienne Bergeron's avatar Etienne Bergeron Committed by Commit Bot

Fix race condition with SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY

This CL is fixing a race condition that can happen when two threads
are trying to load a DLL at the same time. The problem was that the
priority boost may happen on the wrong thread
(see: https://crbug.com/1124759).

The solution is to add a std::atomic_bool already_loaded and set the
flag when exiting the scope. It is fine that two threads enter the
scope and try to load the DLL at the same time. Both thread will have
and increased thread priority.

R=gab@chromium.org, fdoray@chromium.org

Bug: 1124759
Change-Id: I650ff99f56f1abeba6175024909cf344cad6bc61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2392746Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Commit-Queue: Etienne Bergeron <etienneb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805083}
parent d8f07bc4
...@@ -12,14 +12,19 @@ namespace base { ...@@ -12,14 +12,19 @@ namespace base {
namespace internal { namespace internal {
ScopedMayLoadLibraryAtBackgroundPriority:: ScopedMayLoadLibraryAtBackgroundPriority::
ScopedMayLoadLibraryAtBackgroundPriority(const Location& from_here) { ScopedMayLoadLibraryAtBackgroundPriority(const Location& from_here,
std::atomic_bool* already_loaded)
#if defined(OS_WIN)
: already_loaded_(already_loaded)
#endif // OS_WIN
{
TRACE_EVENT_BEGIN2("base", "ScopedMayLoadLibraryAtBackgroundPriority", TRACE_EVENT_BEGIN2("base", "ScopedMayLoadLibraryAtBackgroundPriority",
"file_name", from_here.file_name(), "function_name", "file_name", from_here.file_name(), "function_name",
from_here.function_name()); from_here.function_name());
}
bool ScopedMayLoadLibraryAtBackgroundPriority::OnScopeEntered() {
#if defined(OS_WIN) #if defined(OS_WIN)
if (already_loaded_ && already_loaded_->load(std::memory_order_relaxed))
return;
const base::ThreadPriority priority = const base::ThreadPriority priority =
PlatformThread::GetCurrentThreadPriority(); PlatformThread::GetCurrentThreadPriority();
if (priority == base::ThreadPriority::BACKGROUND) { if (priority == base::ThreadPriority::BACKGROUND) {
...@@ -31,8 +36,6 @@ bool ScopedMayLoadLibraryAtBackgroundPriority::OnScopeEntered() { ...@@ -31,8 +36,6 @@ bool ScopedMayLoadLibraryAtBackgroundPriority::OnScopeEntered() {
"ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased"); "ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased");
} }
#endif // OS_WIN #endif // OS_WIN
return true;
} }
ScopedMayLoadLibraryAtBackgroundPriority:: ScopedMayLoadLibraryAtBackgroundPriority::
...@@ -46,6 +49,9 @@ ScopedMayLoadLibraryAtBackgroundPriority:: ...@@ -46,6 +49,9 @@ ScopedMayLoadLibraryAtBackgroundPriority::
"ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased"); "ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased");
PlatformThread::SetCurrentThreadPriority(original_thread_priority_.value()); PlatformThread::SetCurrentThreadPriority(original_thread_priority_.value());
} }
if (already_loaded_)
already_loaded_->store(true, std::memory_order_relaxed);
#endif // OS_WIN #endif // OS_WIN
TRACE_EVENT_END0("base", "ScopedMayLoadLibraryAtBackgroundPriority"); TRACE_EVENT_END0("base", "ScopedMayLoadLibraryAtBackgroundPriority");
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef BASE_THREADING_SCOPED_THREAD_PRIORITY_H_ #ifndef BASE_THREADING_SCOPED_THREAD_PRIORITY_H_
#define BASE_THREADING_SCOPED_THREAD_PRIORITY_H_ #define BASE_THREADING_SCOPED_THREAD_PRIORITY_H_
#include <atomic>
#include "base/base_export.h" #include "base/base_export.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/location.h" #include "base/location.h"
...@@ -38,56 +40,45 @@ enum class ThreadPriority : int; ...@@ -38,56 +40,45 @@ enum class ThreadPriority : int;
// } // }
// Bar(); // Bar();
// //
// The macro raises the thread priority to NORMAL for the scope when first // The macro raises the thread priority to NORMAL for the scope if no other
// encountered. On Windows, loading a DLL on a background thread can lead to a // thread has completed the current scope already (multiple threads can racily
// priority inversion on the loader lock and cause huge janks. // begin the initialization and will all be boosted for it). On Windows, loading
#define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY() \ // a DLL on a background thread can lead to a priority inversion on the loader
base::internal::ScopedMayLoadLibraryAtBackgroundPriority \ // lock and cause huge janks.
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \ #define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY() \
scoped_may_load_library_at_background_priority)(FROM_HERE); \ static std::atomic_bool INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
{ \ already_loaded){false}; \
/* Thread-safe static local variable initialization ensures that */ \ base::internal::ScopedMayLoadLibraryAtBackgroundPriority \
/* OnScopeEntered() is only invoked the first time that this is */ \ INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
/* encountered. */ \ scoped_may_load_library_at_background_priority)( \
static bool INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(invoke_once) = \ FROM_HERE, \
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \ &INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(already_loaded));
scoped_may_load_library_at_background_priority) \
.OnScopeEntered(); \
ALLOW_UNUSED_LOCAL( \
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(invoke_once)); \
}
// Like SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY, but raises the thread // Like SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY, but raises the thread
// priority every time the scope is entered. Use this around code that may // priority every time the scope is entered. Use this around code that may
// conditionally load a DLL each time it is executed, or which repeatedly loads // conditionally load a DLL each time it is executed, or which repeatedly loads
// and unloads DLLs. // and unloads DLLs.
#define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY() \ #define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY() \
base::internal::ScopedMayLoadLibraryAtBackgroundPriority \ base::internal::ScopedMayLoadLibraryAtBackgroundPriority \
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \ INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
scoped_may_load_library_at_background_priority)(FROM_HERE); \ scoped_may_load_library_at_background_priority)(FROM_HERE, nullptr);
{ \
INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
scoped_may_load_library_at_background_priority) \
.OnScopeEntered(); \
}
namespace internal { namespace internal {
class BASE_EXPORT ScopedMayLoadLibraryAtBackgroundPriority { class BASE_EXPORT ScopedMayLoadLibraryAtBackgroundPriority {
public: public:
explicit ScopedMayLoadLibraryAtBackgroundPriority(const Location& from_here); // Boosts thread priority to NORMAL within its scope if |already_loaded| is
// nullptr or set to false.
explicit ScopedMayLoadLibraryAtBackgroundPriority(
const Location& from_here,
std::atomic_bool* already_loaded);
~ScopedMayLoadLibraryAtBackgroundPriority(); ~ScopedMayLoadLibraryAtBackgroundPriority();
// The SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY() macro invokes this
// the first time that it is encountered. The
// SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY() macro invokes
// this every time it is encountered.
bool OnScopeEntered();
private: private:
#if defined(OS_WIN) #if defined(OS_WIN)
// The original priority when invoking OnScopeEntered(). // The original priority when invoking entering the scope().
base::Optional<ThreadPriority> original_thread_priority_; base::Optional<ThreadPriority> original_thread_priority_;
std::atomic_bool* const already_loaded_;
#endif #endif
DISALLOW_COPY_AND_ASSIGN(ScopedMayLoadLibraryAtBackgroundPriority); DISALLOW_COPY_AND_ASSIGN(ScopedMayLoadLibraryAtBackgroundPriority);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment