[base] Enable priority inheritance mutexes on Android
This commit adds support for priority inheritance mutexes on all Linux platforms but enables it only on Android at build time. The feature is enabled only on kernel versions >= 6.1 and is further gated behind a feature flag to monitor its effect prior to full enablement. Currently, the background priority worker threads feature is purely a function of if priority inheritance locks are supported. But in order to gather field data on the effects of running worker threads at background priority once priority inheritance mutexes are enabled, the two features need to be decoupled. Thus, the enablement of background priority worker threads is gated behind a separate feature flag. Bug: 384902323 Change-Id: I2a59549aaedfb27127f241103ab05a7f95d8c631 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6299348 Commit-Queue: Anand Ravi <anandrv@google.com> Reviewed-by: Matthew Denton <mpdenton@chromium.org> Reviewed-by: Benoit Lize <lizeb@chromium.org> Cr-Commit-Position: refs/heads/main@{#1438818}
This commit is contained in:
parent
4f7ce49074
commit
9d185cd68f
@ -73,7 +73,12 @@ declare_args() {
|
||||
# Set to true to enable mutex priority inheritance. See the comments in
|
||||
# LockImpl::PriorityInheritanceAvailable() in lock_impl_posix.cc for the
|
||||
# platform requirements to safely enable priority inheritance.
|
||||
enable_mutex_priority_inheritance = false
|
||||
#
|
||||
# Enabled only on 64-bit Android. On 32 bit Android, PI mutexes have an extra
|
||||
# level of indirection, so we don't bother enabling it. See:
|
||||
# https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:bionic/libc/bionic/pthread_mutex.cpp;drc=9108f258adad329a537e10461fbd526d5b9ad8bf;l=242
|
||||
enable_mutex_priority_inheritance =
|
||||
is_android && (current_cpu == "x64" || current_cpu == "arm64")
|
||||
|
||||
# Control whether the ios stack sampling profiler is enabled. This flag is
|
||||
# only supported on iOS 64-bit architecture, but some project build //base
|
||||
@ -86,7 +91,8 @@ declare_args() {
|
||||
# Chromecast builds have full control over the platform and ensure that
|
||||
# the kernel and glibc versions used have patched the vulnerabilities,
|
||||
# so it is safe to use mutex priority inheritance on Chromecast platform.
|
||||
assert(!enable_mutex_priority_inheritance || is_castos || is_cast_android,
|
||||
assert(!enable_mutex_priority_inheritance ||
|
||||
(is_castos || is_cast_android || is_android),
|
||||
"Do not enable PI mutexes without consulting the security team")
|
||||
|
||||
assert(!is_nacl || is_nacl_saigo,
|
||||
|
@ -39,6 +39,10 @@ BASE_EXPORT BASE_DECLARE_FEATURE(
|
||||
BASE_EXPORT BASE_DECLARE_FEATURE(kPostGetMyMemoryStateToBackground);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
BASE_EXPORT BASE_DECLARE_FEATURE(kUsePriorityInheritanceMutex);
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
|
||||
// Policy for emitting profiler metadata from `ThreadController`.
|
||||
enum class EmitThreadControllerProfilerMetadata {
|
||||
// Always emit metadata.
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "base/memory/raw_ptr_exclusion.h"
|
||||
#include "base/memory/stack_allocated.h"
|
||||
#include "base/synchronization/lock_subtle.h"
|
||||
#include "base/synchronization/synchronization_buildflags.h"
|
||||
#include "base/thread_annotations.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
@ -323,6 +324,15 @@ class [[nodiscard]] SCOPED_LOCKABLE BasicReleasableAutoLock {
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
BASE_EXPORT bool ResetUsePriorityInheritanceMutexForTesting();
|
||||
|
||||
// Check to see whether the current kernel supports priority inheritance
|
||||
// properly by adjusting process priorities to boost the futex owner.
|
||||
BASE_EXPORT bool KernelSupportsPriorityInheritanceFutex();
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_SYNCHRONIZATION_LOCK_IMPL_H_
|
||||
|
@ -4,19 +4,84 @@
|
||||
|
||||
#include "base/synchronization/lock_impl.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "base/check_op.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/features.h"
|
||||
#include "base/posix/safe_strerror.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/synchronization/synchronization_buildflags.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base::internal {
|
||||
// On Android, `pthread_mutexattr_setprotocol()` is only defined in bionic
|
||||
// starting with API level 28. Make it a weak import, so that we can compile.
|
||||
extern "C" {
|
||||
int __attribute__((weak)) pthread_mutexattr_setprotocol(
|
||||
pthread_mutexattr_t* _Nonnull __attr,
|
||||
int __protocol);
|
||||
}
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace features {
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
BASE_FEATURE(kUsePriorityInheritanceMutex,
|
||||
"UsePriorityInheritanceMutex",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
} // namespace features
|
||||
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
enum class UsePriorityInheritanceMutex : uint8_t {
|
||||
kUnknown = 0,
|
||||
kEnabled = 1,
|
||||
kDisabled = 2,
|
||||
};
|
||||
|
||||
static constinit std::atomic<UsePriorityInheritanceMutex>
|
||||
s_pi_enablement_status{UsePriorityInheritanceMutex::kUnknown};
|
||||
|
||||
bool IsMutexPriorityInheritanceEnabled() {
|
||||
// The atomic operations in this method are idempotent and do not imply
|
||||
// memory synchronization, so no need for anything else than relaxed ordering.
|
||||
auto status = s_pi_enablement_status.load(std::memory_order_relaxed);
|
||||
if (status != UsePriorityInheritanceMutex::kUnknown) {
|
||||
return status == UsePriorityInheritanceMutex::kEnabled;
|
||||
}
|
||||
|
||||
if (!base::FeatureList::GetInstance()) {
|
||||
// The feature list is unavailable, so return false but don't cache the
|
||||
// result.
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: The order of the checks matters here. The FeatureList is queried
|
||||
// after checking for all other criteria so that the control and experiment
|
||||
// groups don't get polluted with clients that cannot support priority
|
||||
// inheriting mutexes
|
||||
bool feature_enabled =
|
||||
(pthread_mutexattr_setprotocol != nullptr) &&
|
||||
KernelSupportsPriorityInheritanceFutex() &&
|
||||
base::FeatureList::IsEnabled(features::kUsePriorityInheritanceMutex);
|
||||
|
||||
s_pi_enablement_status.store(feature_enabled
|
||||
? UsePriorityInheritanceMutex::kEnabled
|
||||
: UsePriorityInheritanceMutex::kDisabled,
|
||||
std::memory_order_relaxed);
|
||||
return feature_enabled;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
const char* AdditionalHintForSystemErrorCode(int error_code) {
|
||||
switch (error_code) {
|
||||
@ -58,7 +123,7 @@ void dcheck_unlock_result(int rv) {
|
||||
// Lock::PriorityInheritanceAvailable still must be checked as the code may
|
||||
// compile but the underlying platform still may not correctly support priority
|
||||
// inheritance locks.
|
||||
#if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
|
||||
#if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_FUCHSIA)
|
||||
#define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 0
|
||||
#else
|
||||
#define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 1
|
||||
@ -98,7 +163,7 @@ void LockImpl::LockInternal() {
|
||||
// static
|
||||
bool LockImpl::PriorityInheritanceAvailable() {
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
return true;
|
||||
return IsMutexPriorityInheritanceEnabled();
|
||||
#elif PRIORITY_INHERITANCE_LOCKS_POSSIBLE() && BUILDFLAG(IS_APPLE)
|
||||
return true;
|
||||
#else
|
||||
@ -121,4 +186,32 @@ bool LockImpl::PriorityInheritanceAvailable() {
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace base::internal
|
||||
} // namespace internal
|
||||
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
bool ResetUsePriorityInheritanceMutexForTesting() {
|
||||
internal::s_pi_enablement_status.store(
|
||||
internal::UsePriorityInheritanceMutex::kUnknown);
|
||||
// Recompute immediately to cache the new value.
|
||||
return internal::IsMutexPriorityInheritanceEnabled();
|
||||
}
|
||||
|
||||
bool KernelSupportsPriorityInheritanceFutex() {
|
||||
// https://android-review.googlesource.com/c/3481472 which fixes priority
|
||||
// inheritance using rt-mutexes in the kernel landed in the 6.12.13 android
|
||||
// kernel and was backported to the 6.1.75 and 6.6.29 kernels. This change
|
||||
// hasn't been upstreamed yet.
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
auto kernel_version = SysInfo::KernelVersionNumber::Current();
|
||||
return (kernel_version > SysInfo::KernelVersionNumber(6, 12, 13)) ||
|
||||
((kernel_version > SysInfo::KernelVersionNumber(6, 6, 29)) &&
|
||||
(kernel_version < SysInfo::KernelVersionNumber(6, 6, INT32_MAX))) ||
|
||||
((kernel_version > SysInfo::KernelVersionNumber(6, 1, 75)) &&
|
||||
(kernel_version < SysInfo::KernelVersionNumber(6, 1, INT32_MAX)));
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
|
||||
} // namespace base
|
||||
|
@ -6,21 +6,36 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/dcheck_is_on.h"
|
||||
#include "base/features.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/functional/callback_helpers.h"
|
||||
#include "base/functional/function_ref.h"
|
||||
#include "base/location.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/raw_ref.h"
|
||||
#include "base/profiler/thread_delegate.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/synchronization/lock_impl.h"
|
||||
#include "base/synchronization/lock_subtle.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "base/test/bind.h"
|
||||
#include "base/test/gtest_util.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/thread_annotations.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/timer/elapsed_timer.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@ -425,6 +440,209 @@ TEST(LockTest, TrackingDisabled) {
|
||||
EXPECT_TRUE(subtle::GetTrackedLocksHeldByCurrentThread().empty());
|
||||
}
|
||||
|
||||
// Priority Inheritance Tests --------------------------------------------------
|
||||
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
namespace {
|
||||
class PriorityInheritanceTest {
|
||||
public:
|
||||
// The average value of MeasureRunTime() over |num_samples| iterations.
|
||||
static TimeDelta MeasureAverageRunTime(int num_samples = 10) {
|
||||
TimeDelta total_runtime;
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
total_runtime += MeasureRunTime();
|
||||
}
|
||||
|
||||
return total_runtime / num_samples;
|
||||
}
|
||||
|
||||
// Measure the time taken for a low-priority thread (kBackground) to perform
|
||||
// CPU bound work when it holds a lock that is awaited by a high-priority
|
||||
// thread (kRealtimeAudio).
|
||||
static TimeDelta MeasureRunTime() {
|
||||
Lock lock;
|
||||
TimeDelta test_run_time;
|
||||
std::atomic<bool> signal_cpu_bound_worker_threads_shutdown{false},
|
||||
signal_thread_a_will_lock{false};
|
||||
|
||||
// Keep all the cores busy with a workload of CPU bound thread to reduce
|
||||
// flakiness in the test by skewing the CPU time between the high-priority
|
||||
// and low-priority measurement threads.
|
||||
std::vector<TestThread> cpu_bound_worker_threads;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
cpu_bound_worker_threads.emplace_back(
|
||||
ThreadType::kDefault, base::BindLambdaForTesting([&]() {
|
||||
while (!signal_cpu_bound_worker_threads_shutdown.load(
|
||||
std::memory_order_relaxed)) {
|
||||
BusyLoop(10);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto& worker_thread : cpu_bound_worker_threads) {
|
||||
worker_thread.Create();
|
||||
}
|
||||
|
||||
TestThread thread_a(
|
||||
ThreadType::kRealtimeAudio, base::BindLambdaForTesting([&]() {
|
||||
// Signal to thread B that the current thread will acquire the lock
|
||||
// next, so that it can to start its CPU bound work.
|
||||
signal_thread_a_will_lock.store(true, std::memory_order_relaxed);
|
||||
|
||||
// Wait on the lock to be released once the low-priority thread is
|
||||
// done. In the case when priority inheritance mutexes are enabled,
|
||||
// this should boost the priority of the low-priority thread to the
|
||||
// priority of the highest priority waiter (i.e. the current thread).
|
||||
AutoLock auto_lock(lock);
|
||||
BusyLoop(10);
|
||||
}));
|
||||
|
||||
TestThread thread_b(
|
||||
ThreadType::kBackground, base::BindLambdaForTesting([&]() {
|
||||
// Acquire the lock before creating the high-priority thread, so that
|
||||
// the higher priority thread is blocked on the current thread while
|
||||
// the current thread performs CPU-bound work.
|
||||
AutoLock auto_lock(lock);
|
||||
thread_a.Create();
|
||||
|
||||
// Before performing the CPU bound work, wait for the thread A to
|
||||
// signal that it has started running and will acquire the lock next.
|
||||
// While it is not a perfectly reliable signal (thread A may get
|
||||
// descheduled immediately after signalling), given the relative
|
||||
// priorities of the two threads it is good enough to reduce large
|
||||
// variations due to latencies in thread bring up.
|
||||
while (!signal_thread_a_will_lock.load(std::memory_order_relaxed)) {
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
ElapsedTimer timer;
|
||||
BusyLoop(1000000);
|
||||
test_run_time = timer.Elapsed();
|
||||
}));
|
||||
|
||||
// Create the low-priority thread which is responsible for creating the
|
||||
// high-priority thread. Wait for both threads to finish before recording
|
||||
// the elapsed time.
|
||||
thread_b.Create();
|
||||
thread_b.Join();
|
||||
thread_a.Join();
|
||||
|
||||
signal_cpu_bound_worker_threads_shutdown.store(true,
|
||||
std::memory_order_relaxed);
|
||||
for (auto& worker_thread : cpu_bound_worker_threads) {
|
||||
worker_thread.Join();
|
||||
}
|
||||
|
||||
return test_run_time;
|
||||
}
|
||||
|
||||
private:
|
||||
// CPU bound work for the threads to eat up CPU cycles.
|
||||
static void BusyLoop(size_t n) {
|
||||
__unused int sum = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (base::ShouldRecordSubsampledMetric(0.5)) {
|
||||
sum += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestThread : public PlatformThread::Delegate {
|
||||
public:
|
||||
explicit TestThread(ThreadType thread_type, base::OnceClosure body)
|
||||
: thread_type_(thread_type), body_(std::move(body)) {}
|
||||
|
||||
void Create() {
|
||||
ASSERT_TRUE(
|
||||
PlatformThread::CreateWithType(0, this, &handle_, thread_type_));
|
||||
}
|
||||
|
||||
void ThreadMain() override { std::move(body_).Run(); }
|
||||
|
||||
void Join() { PlatformThread::Join(handle_); }
|
||||
|
||||
private:
|
||||
ThreadType thread_type_;
|
||||
PlatformThreadHandle handle_;
|
||||
base::OnceClosure body_;
|
||||
};
|
||||
};
|
||||
|
||||
class ScopedConfigureUsePriorityInheritanceMutex {
|
||||
public:
|
||||
explicit ScopedConfigureUsePriorityInheritanceMutex(bool enabled) {
|
||||
feature_list_.InitWithFeatureState(features::kUsePriorityInheritanceMutex,
|
||||
enabled);
|
||||
ResetUsePriorityInheritanceMutexForTesting();
|
||||
}
|
||||
|
||||
~ScopedConfigureUsePriorityInheritanceMutex() {
|
||||
feature_list_.Reset();
|
||||
ResetUsePriorityInheritanceMutexForTesting();
|
||||
}
|
||||
|
||||
private:
|
||||
test::ScopedFeatureList feature_list_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Tests that the time taken by a higher-priority thread to acquire a lock held
|
||||
// by a lower-priority thread is indeed reduced by priority inheritance.
|
||||
TEST(LockTest, PriorityIsInherited) {
|
||||
TimeDelta avg_test_run_time_with_pi, avg_test_run_time_without_pi;
|
||||
|
||||
{
|
||||
ScopedConfigureUsePriorityInheritanceMutex config_use_pi_mutex(true);
|
||||
|
||||
// Priority inheritance mutexes are not supported on Android kernels < 6.1
|
||||
if (!Lock::HandlesMultipleThreadPriorities()) {
|
||||
GTEST_SKIP() << "base::Lock does not handle multiple thread priorities "
|
||||
<< "(Kernel version: "
|
||||
<< base::SysInfo::KernelVersionNumber::Current() << ")";
|
||||
}
|
||||
|
||||
avg_test_run_time_with_pi =
|
||||
PriorityInheritanceTest::MeasureAverageRunTime();
|
||||
}
|
||||
|
||||
{
|
||||
ScopedConfigureUsePriorityInheritanceMutex config_use_pi_mutex(false);
|
||||
|
||||
avg_test_run_time_without_pi =
|
||||
PriorityInheritanceTest::MeasureAverageRunTime();
|
||||
}
|
||||
|
||||
// During the time in which the thread A is waiting on the lock to be released
|
||||
// by the thread B, the thread B runs at kBackground priority in the non-PI
|
||||
// case and at kRealtimeAudio priority in the PI case.
|
||||
//
|
||||
// Based on the Linux kernel's allocation of CPU shares documented in
|
||||
// https://elixir.bootlin.com/linux/v6.12.5/source/kernel/sched/core.c#L9998,
|
||||
// a thread running at kRealtimeAudio (nice value = -16) gets 36291 shares
|
||||
// of the CPU, a thread at kDefault (nice value = 0) get 1024 shares and a
|
||||
// thread at kBackground (nice value = 10) gets 110 shares of the CPU.
|
||||
//
|
||||
// Assuming no other threads except the ones created by this test are running,
|
||||
// during the time in which thread A is waiting on the lock to be released by
|
||||
// thread B, thread B gets 110/(15*1024 + 110) ≈ 0.7% of the CPU time in the
|
||||
// non-PI case and 36291/(36291 + 15*1024) ≈ 70% of the CPU time in the PI
|
||||
// case. This is approximately a 100x difference in CPU shares allocated to
|
||||
// the thread B when it is doing CPU-bound work.
|
||||
//
|
||||
// The test is thus designed such that the measured run time is thread B's CPU
|
||||
// bound work. While there are other factors at play that determine the
|
||||
// measured run time such as the frequency at which the CPU is running, we can
|
||||
// expect that there will be at least an order of magnitude of disparity in
|
||||
// the test run times with and without PI.
|
||||
//
|
||||
// In order to reduce test flakiness while still eliminating the possibility
|
||||
// of variance in measurements accounting for the test results, we
|
||||
// conservatively expect a 3x improvement.
|
||||
EXPECT_GT(avg_test_run_time_without_pi, 3 * avg_test_run_time_with_pi);
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
|
||||
#endif // DCHECK_IS_ON()
|
||||
|
||||
} // namespace base
|
||||
|
@ -7,11 +7,41 @@
|
||||
#include "base/base_switches.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/synchronization/lock_impl.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base::internal {
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
BASE_FEATURE(kForceBackgroundPriorityForWorkerThreads,
|
||||
"ForceBackgroundPriorityForWorkerThreads",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
BASE_FEATURE(kUseBackgroundPriorityForWorkerThreads,
|
||||
"UseBackgroundPriorityForWorkerThreads",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
const base::Feature& FeatureControllingBackgroundPriorityWorkerThreads() {
|
||||
// There are two mutually exclusive feature flags that control whether the
|
||||
// thread runs at background priority on Android:
|
||||
// - |kUseBackgroundPriorityForWorkerThreads|: For newer kernels that support
|
||||
// priority inheritance mutexes, to be used in conjunction with
|
||||
// |kUsePriorityInheritanceMutex|.
|
||||
// - |kForceBackgroundPriorityForWorkerThreads|: For older kernels that do not
|
||||
// support priority inheritance mutexes.
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
if (base::KernelSupportsPriorityInheritanceFutex()) {
|
||||
return kUseBackgroundPriorityForWorkerThreads;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
return kForceBackgroundPriorityForWorkerThreads;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
namespace {
|
||||
|
||||
bool CanUseBackgroundThreadTypeForWorkerThreadImpl() {
|
||||
@ -23,6 +53,10 @@ bool CanUseBackgroundThreadTypeForWorkerThreadImpl() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
return base::FeatureList::IsEnabled(
|
||||
FeatureControllingBackgroundPriorityWorkerThreads());
|
||||
#else // BUILDFLAG(IS_ANDROID)
|
||||
// When Lock doesn't handle multiple thread priorities, run all
|
||||
// WorkerThread with a normal priority to avoid priority inversion when a
|
||||
// thread running with a normal priority tries to acquire a lock held by a
|
||||
@ -31,7 +65,6 @@ bool CanUseBackgroundThreadTypeForWorkerThreadImpl() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
// When thread type can't be increased to kNormal, run all threads with a
|
||||
// kNormal thread type to avoid priority inversions on shutdown
|
||||
// (ThreadPoolImpl increases kBackground threads type to kNormal on shutdown
|
||||
@ -42,9 +75,9 @@ bool CanUseBackgroundThreadTypeForWorkerThreadImpl() {
|
||||
ThreadType::kDefault)) {
|
||||
return false;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
return true;
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
}
|
||||
|
||||
bool CanUseUtilityThreadTypeForWorkerThreadImpl() {
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/task/task_traits.h"
|
||||
#include "base/threading/thread.h"
|
||||
|
||||
@ -54,6 +55,11 @@ bool BASE_EXPORT CanUseBackgroundThreadTypeForWorkerThread();
|
||||
// utility thread type.
|
||||
bool BASE_EXPORT CanUseUtilityThreadTypeForWorkerThread();
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
const base::Feature& BASE_EXPORT
|
||||
FeatureControllingBackgroundPriorityWorkerThreads();
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
} // namespace internal
|
||||
} // namespace base
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "base/task/thread_pool/environment_config.h"
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "build/build_config.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@ -13,7 +14,13 @@ namespace base::internal {
|
||||
TEST(ThreadPoolEnvironmentConfig, CanUseBackgroundPriorityForWorker) {
|
||||
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE)
|
||||
EXPECT_TRUE(CanUseBackgroundThreadTypeForWorkerThread());
|
||||
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || \
|
||||
#elif BUILDFLAG(IS_ANDROID)
|
||||
// On Android, use of background thread priority is controlled by one of two
|
||||
// feature flags.
|
||||
EXPECT_EQ(CanUseBackgroundThreadTypeForWorkerThread(),
|
||||
base::FeatureList::IsEnabled(
|
||||
FeatureControllingBackgroundPriorityWorkerThreads()));
|
||||
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_FUCHSIA) || \
|
||||
BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_NACL)
|
||||
EXPECT_FALSE(CanUseBackgroundThreadTypeForWorkerThread());
|
||||
#else
|
||||
|
@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/synchronization/lock_impl.h"
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
@ -32,10 +32,13 @@
|
||||
#include <tuple>
|
||||
|
||||
#include "base/clang_profiling_buildflags.h"
|
||||
#include "base/features.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "build/build_config.h"
|
||||
#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h"
|
||||
#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
|
||||
#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
|
||||
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
|
||||
@ -365,31 +368,67 @@ BPF_TEST_C(BaselinePolicy, FutexEINVAL, BaselinePolicy) {
|
||||
}
|
||||
}
|
||||
#else
|
||||
BPF_DEATH_TEST_C(BaselinePolicy,
|
||||
FutexWithRequeuePriorityInheritence,
|
||||
DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
|
||||
BaselinePolicy) {
|
||||
syscall(__NR_futex, nullptr, FUTEX_CMP_REQUEUE_PI, 0, nullptr, nullptr, 0);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
BPF_DEATH_TEST_C(BaselinePolicy,
|
||||
FutexWithRequeuePriorityInheritencePrivate,
|
||||
DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
|
||||
BaselinePolicy) {
|
||||
syscall(__NR_futex, nullptr, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, nullptr,
|
||||
nullptr, 0);
|
||||
_exit(1);
|
||||
}
|
||||
#define TEST_BASELINE_PI_FUTEX(op) \
|
||||
_TEST_BASELINE_PI_FUTEX(BaselinePolicy, Futex_##op) { \
|
||||
syscall(__NR_futex, nullptr, op, 0, nullptr, nullptr, 0); \
|
||||
_exit(1); \
|
||||
}
|
||||
|
||||
BPF_DEATH_TEST_C(BaselinePolicy,
|
||||
FutexWithUnlockPIPrivate,
|
||||
DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
|
||||
BaselinePolicy) {
|
||||
syscall(__NR_futex, nullptr, FUTEX_UNLOCK_PI_PRIVATE, 0, nullptr, nullptr, 0);
|
||||
_exit(1);
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
// PI futexes are only allowed by the sandbox on kernels >= 6.1 and if the
|
||||
// kUsePriorityInheritanceMutex is enabled. In order to test this,
|
||||
// |_TEST_BASELINE_PI_FUTEX| generates a test which has two parts:
|
||||
// - The first part of the test enables the feature and performs the futex
|
||||
// syscall in a child process with the provided futex operation. Then it
|
||||
// asserts that the syscall succeed only if the kernel version is at
|
||||
// least 6.1.
|
||||
// - The second part of the test disables the feature and performs the futex
|
||||
// syscall in a child process with the provided futex operation. Then it
|
||||
// asserts that the syscall always crashes the process.
|
||||
#define _TEST_BASELINE_PI_FUTEX(test_case_name, test_name) \
|
||||
void BPF_TEST_PI_FUTEX_##test_name(); \
|
||||
TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \
|
||||
__TEST_BASELINE_PI_FUTEX(BPF_TEST_PI_FUTEX_##test_name, true); \
|
||||
__TEST_BASELINE_PI_FUTEX(BPF_TEST_PI_FUTEX_##test_name, false); \
|
||||
} \
|
||||
void BPF_TEST_PI_FUTEX_##test_name()
|
||||
|
||||
#define __TEST_BASELINE_PI_FUTEX(test_name, use_pi_mutex) \
|
||||
{ \
|
||||
base::test::ScopedFeatureList feature_; \
|
||||
feature_.InitWithFeatureState( \
|
||||
base::features::kUsePriorityInheritanceMutex, use_pi_mutex); \
|
||||
sandbox::SandboxBPFTestRunner bpf_test_runner( \
|
||||
new BPFTesterSimpleDelegate<BaselinePolicy>(test_name)); \
|
||||
sandbox::UnitTests::RunTestInProcess( \
|
||||
&bpf_test_runner, PIFutexDeath, \
|
||||
static_cast<const void*>(GetFutexErrorMessageContentForTests())); \
|
||||
}
|
||||
|
||||
void PIFutexDeath(int status, const std::string& msg, const void* aux) {
|
||||
if (base::KernelSupportsPriorityInheritanceFutex() &&
|
||||
base::FeatureList::IsEnabled(
|
||||
base::features::kUsePriorityInheritanceMutex)) {
|
||||
sandbox::UnitTests::DeathSuccess(status, msg, nullptr);
|
||||
} else {
|
||||
sandbox::UnitTests::DeathSEGVMessage(status, msg, aux);
|
||||
}
|
||||
}
|
||||
#endif // defined(LIBC_GLIBC) && !BUILDFLAG(IS_CHROMEOS)
|
||||
#else
|
||||
#define _TEST_BASELINE_PI_FUTEX(test_case_name, test_name) \
|
||||
BPF_DEATH_TEST_C(BaselinePolicy, test_name, \
|
||||
DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), \
|
||||
BaselinePolicy)
|
||||
#endif
|
||||
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_LOCK_PI)
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_LOCK_PI2)
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_TRYLOCK_PI)
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_WAIT_REQUEUE_PI)
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_CMP_REQUEUE_PI)
|
||||
TEST_BASELINE_PI_FUTEX(FUTEX_UNLOCK_PI_PRIVATE)
|
||||
#endif
|
||||
|
||||
BPF_TEST_C(BaselinePolicy, PrctlDumpable, BaselinePolicy) {
|
||||
const int is_dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0);
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/features.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/synchronization/synchronization_buildflags.h"
|
||||
#include "build/build_config.h"
|
||||
@ -339,17 +341,26 @@ ResultExpr RestrictKillTarget(pid_t target_pid, int sysno) {
|
||||
|
||||
ResultExpr RestrictFutex() {
|
||||
const uint64_t kAllowedFutexFlags = FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME;
|
||||
ResultExpr error = IsBuggyGlibcSemPost() ? Error(EINVAL) : CrashSIGSYSFutex();
|
||||
const Arg<int> op(1);
|
||||
return Switch(op & ~kAllowedFutexFlags)
|
||||
.Cases({FUTEX_WAIT, FUTEX_WAKE, FUTEX_REQUEUE, FUTEX_CMP_REQUEUE,
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
// Enable priority-inheritance operations.
|
||||
FUTEX_LOCK_PI, FUTEX_UNLOCK_PI, FUTEX_TRYLOCK_PI,
|
||||
FUTEX_WAIT_REQUEUE_PI, FUTEX_CMP_REQUEUE_PI,
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
FUTEX_WAKE_OP, FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET},
|
||||
Allow())
|
||||
.Default(IsBuggyGlibcSemPost() ? Error(EINVAL) : CrashSIGSYSFutex());
|
||||
#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
// Priority-inheritance futex operations are enabled only on Android
|
||||
// kernels 6.1+. Bionic uses the PI variants of the futex operations
|
||||
// (FUTEX_LOCK_PI2, FUTEX_UNLOCK_PI) to implement priority inheriting
|
||||
// mutexes.
|
||||
.Cases({FUTEX_LOCK_PI, FUTEX_UNLOCK_PI, FUTEX_TRYLOCK_PI,
|
||||
FUTEX_WAIT_REQUEUE_PI, FUTEX_CMP_REQUEUE_PI, FUTEX_LOCK_PI2},
|
||||
(base::KernelSupportsPriorityInheritanceFutex() &&
|
||||
base::FeatureList::IsEnabled(
|
||||
base::features::kUsePriorityInheritanceMutex))
|
||||
? Allow()
|
||||
: error)
|
||||
#endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
|
||||
.Default(error);
|
||||
}
|
||||
|
||||
ResultExpr RestrictGetSetpriority(pid_t target_pid) {
|
||||
|
@ -59,6 +59,10 @@
|
||||
#define FUTEX_CMP_REQUEUE_PI 12
|
||||
#endif
|
||||
|
||||
#if !defined(FUTEX_LOCK_PI2)
|
||||
#define FUTEX_LOCK_PI2 13
|
||||
#endif
|
||||
|
||||
#if !defined(FUTEX_PRIVATE_FLAG)
|
||||
#define FUTEX_PRIVATE_FLAG 128
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user