Commit 7b97c324 authored by reveman's avatar reveman Committed by Commit bot

base: Allow renderer thread priorities to be changed.

This provides a mechanism for a renderer to change the priority of
its threads and as a result also the cpuset they are assigned to.

A ChildProcessHost IPC message from the renderer is used to request
a thread priority change and have the browser process identify the
renderer thread that is requesting a priority change by checking all
its threads to find a thread with NSpid field in
/proc/[pid]/task/[thread_id]/status that matches the namespace tid
from the renderer.

This is currently limited to Linux and ChromeOS but follow up work
will investigate the possibility and benefits of doing the same on
other platforms.

Note: Thread priorities in the renderer are already adjusted in a
similar way on Android as the sandbox is not strict enough to
prevent this on Android today.

BUG=chrome-os-partner:56550
TEST=

Review-Url: https://codereview.chromium.org/2334533002
Cr-Commit-Position: refs/heads/master@{#419649}
parent e11af1f7
......@@ -20,6 +20,8 @@
#include "base/files/file_util.h"
#include "base/memory/singleton.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
......@@ -72,6 +74,28 @@ class LinuxDistroHelper {
};
#endif // if defined(OS_LINUX)
bool GetTasksForProcess(pid_t pid, std::vector<pid_t>* tids) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
DIR* task = opendir(buf);
if (!task) {
DLOG(WARNING) << "Cannot open " << buf;
return false;
}
struct dirent* dent;
while ((dent = readdir(task))) {
char* endptr;
const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10);
if (tid_ul == ULONG_MAX || *endptr)
continue;
tids->push_back(tid_ul);
}
closedir(task);
return true;
}
} // namespace
namespace base {
......@@ -132,34 +156,17 @@ void SetLinuxDistro(const std::string& distro) {
pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
bool* syscall_supported) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
if (syscall_supported != NULL)
*syscall_supported = false;
DIR* task = opendir(buf);
if (!task) {
DLOG(WARNING) << "Cannot open " << buf;
return -1;
}
std::vector<pid_t> tids;
struct dirent* dent;
while ((dent = readdir(task))) {
char* endptr;
const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10);
if (tid_ul == ULONG_MAX || *endptr)
continue;
tids.push_back(tid_ul);
}
closedir(task);
if (!GetTasksForProcess(pid, &tids))
return -1;
std::unique_ptr<char[]> syscall_data(new char[expected_data.length()]);
for (std::vector<pid_t>::const_iterator
i = tids.begin(); i != tids.end(); ++i) {
const pid_t current_tid = *i;
snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, current_tid);
for (pid_t tid : tids) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, tid);
int fd = open(buf, O_RDONLY);
if (fd < 0)
continue;
......@@ -172,7 +179,45 @@ pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
if (0 == strncmp(expected_data.c_str(), syscall_data.get(),
expected_data.length())) {
return current_tid;
return tid;
}
}
return -1;
}
pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported) {
if (ns_pid_supported)
*ns_pid_supported = false;
std::vector<pid_t> tids;
if (!GetTasksForProcess(pid, &tids))
return -1;
for (pid_t tid : tids) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task/%d/status", pid, tid);
std::string status;
if (!ReadFileToString(FilePath(buf), &status))
return -1;
StringPairs pairs;
SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
for (const auto& pair : pairs) {
const std::string& key = pair.first;
const std::string& value_str = pair.second;
if (key == "NSpid") {
if (ns_pid_supported)
*ns_pid_supported = true;
std::vector<StringPiece> split_value_str = SplitStringPiece(
value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
DCHECK_NE(split_value_str.size(), 0u);
int value;
// The last value in the list is the PID in the namespace.
if (StringToInt(split_value_str.back(), &value) && value == ns_tid) {
// The first value in the list is the real PID.
if (StringToInt(split_value_str.front(), &value))
return value;
}
}
}
}
return -1;
......
......@@ -33,6 +33,12 @@ BASE_EXPORT pid_t FindThreadIDWithSyscall(pid_t pid,
const std::string& expected_data,
bool* syscall_supported);
// For a given process |pid|, look through all its threads and find the first
// thread with /proc/[pid]/task/[thread_id]/status where NSpid matches |ns_tid|.
// Returns the thread id or -1 on error. If |ns_pid_supported| is
// set to false the kernel does not support NSpid in procfs.
BASE_EXPORT pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported);
} // namespace base
#endif // BASE_LINUX_UTIL_H_
......@@ -205,6 +205,20 @@ class BASE_EXPORT PlatformThread {
static ThreadPriority GetCurrentThreadPriority();
#if defined(OS_LINUX)
// Toggles a specific thread's priority at runtime. This can be used to
// change the priority of a thread in a different process and will fail
// if the calling process does not have proper permissions. The
// SetCurrentThreadPriority() function above is preferred in favor of
// security but on platforms where sandboxed processes are not allowed to
// change priority this function exists to allow a non-sandboxed process
// to change the priority of sandboxed threads for improved performance.
// Warning: Don't use this for a main thread because that will change the
// whole thread group's (i.e. process) priority.
static void SetThreadPriority(PlatformThreadId thread_id,
ThreadPriority priority);
#endif
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformThread);
};
......
......@@ -8,8 +8,10 @@
#include <sched.h>
#include <stddef.h>
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
#include "base/tracked_objects.h"
......@@ -18,11 +20,47 @@
#if !defined(OS_NACL)
#include <pthread.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
namespace base {
namespace {
#if !defined(OS_NACL)
const FilePath::CharType kCpusetDirectory[] =
FILE_PATH_LITERAL("/sys/fs/cgroup/cpuset/chrome");
FilePath ThreadPriorityToCpusetDirectory(ThreadPriority priority) {
FilePath cpuset_filepath(kCpusetDirectory);
switch (priority) {
case ThreadPriority::NORMAL:
return cpuset_filepath;
case ThreadPriority::BACKGROUND:
return cpuset_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
case ThreadPriority::DISPLAY:
case ThreadPriority::REALTIME_AUDIO:
return cpuset_filepath.Append(FILE_PATH_LITERAL("urgent"));
}
NOTREACHED();
return FilePath();
}
void SetThreadCpuset(PlatformThreadId thread_id,
const FilePath& cpuset_directory) {
// Silently ignore request if cpuset directory doesn't exist.
if (!DirectoryExists(cpuset_directory))
return;
FilePath tasks_filepath = cpuset_directory.Append(FILE_PATH_LITERAL("tasks"));
std::string tid = IntToString(thread_id);
int bytes_written = WriteFile(tasks_filepath, tid.c_str(), tid.size());
if (bytes_written != static_cast<int>(tid.size())) {
DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
}
}
#endif
} // namespace
namespace internal {
......@@ -41,6 +79,8 @@ const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4] = {
bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) {
#if !defined(OS_NACL)
FilePath cpuset_directory = ThreadPriorityToCpusetDirectory(priority);
SetThreadCpuset(PlatformThread::CurrentId(), cpuset_directory);
return priority == ThreadPriority::REALTIME_AUDIO &&
pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0;
#else
......@@ -90,6 +130,26 @@ void PlatformThread::SetName(const std::string& name) {
#endif // !defined(OS_NACL)
}
#if !defined(OS_NACL)
// static
void PlatformThread::SetThreadPriority(PlatformThreadId thread_id,
ThreadPriority priority) {
// Changing current main threads' priority is not permitted in favor of
// security, this interface is restricted to change only non-main thread
// priority.
CHECK_NE(thread_id, getpid());
FilePath cpuset_directory = ThreadPriorityToCpusetDirectory(priority);
SetThreadCpuset(thread_id, cpuset_directory);
const int nice_setting = internal::ThreadPriorityToNiceValue(priority);
if (setpriority(PRIO_PROCESS, thread_id, nice_setting)) {
DVPLOG(1) << "Failed to set nice value of thread (" << thread_id << ") to "
<< nice_setting;
}
}
#endif // !defined(OS_NACL)
void InitThreading() {}
void TerminateOnThread() {}
......
......@@ -89,6 +89,11 @@
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#endif
#if defined(OS_LINUX)
#include "base/linux_util.h"
#include "base/threading/platform_thread.h"
#endif
namespace content {
namespace {
......@@ -204,6 +209,10 @@ bool RenderMessageFilter::OnMessageReceived(const IPC::Message& message) {
OnAllocateLockedDiscardableSharedMemory)
IPC_MESSAGE_HANDLER(ChildProcessHostMsg_DeletedDiscardableSharedMemory,
OnDeletedDiscardableSharedMemory)
#if defined(OS_LINUX)
IPC_MESSAGE_HANDLER(ChildProcessHostMsg_SetThreadPriority,
OnSetThreadPriority)
#endif
IPC_MESSAGE_HANDLER_DELAY_REPLY(RenderProcessHostMsg_Keygen, OnKeygen)
IPC_MESSAGE_HANDLER(RenderProcessHostMsg_DidGenerateCacheableMetadata,
OnCacheableMetadataAvailable)
......@@ -414,6 +423,35 @@ void RenderMessageFilter::OnDeletedDiscardableSharedMemory(
this, id));
}
#if defined(OS_LINUX)
void RenderMessageFilter::SetThreadPriorityOnFileThread(
base::PlatformThreadId ns_tid,
base::ThreadPriority priority) {
bool ns_pid_supported = false;
pid_t peer_tid = base::FindThreadID(peer_pid(), ns_tid, &ns_pid_supported);
if (peer_tid == -1) {
if (ns_pid_supported)
DLOG(WARNING) << "Could not find tid";
return;
}
if (peer_tid == peer_pid()) {
DLOG(WARNING) << "Changing priority of main thread is not allowed";
return;
}
base::PlatformThread::SetThreadPriority(peer_tid, priority);
}
void RenderMessageFilter::OnSetThreadPriority(base::PlatformThreadId ns_tid,
base::ThreadPriority priority) {
BrowserThread::PostTask(
BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
base::Bind(&RenderMessageFilter::SetThreadPriorityOnFileThread, this,
ns_tid, priority));
}
#endif
void RenderMessageFilter::OnCacheableMetadataAvailable(
const GURL& url,
base::Time expected_response_time,
......
......@@ -171,6 +171,13 @@ class CONTENT_EXPORT RenderMessageFilter : public BrowserMessageFilter {
void DeletedDiscardableSharedMemoryOnFileThread(DiscardableSharedMemoryId id);
void OnDeletedDiscardableSharedMemory(DiscardableSharedMemoryId id);
#if defined(OS_LINUX)
void SetThreadPriorityOnFileThread(base::PlatformThreadId ns_tid,
base::ThreadPriority priority);
void OnSetThreadPriority(base::PlatformThreadId ns_tid,
base::ThreadPriority priority);
#endif
void OnCacheableMetadataAvailable(const GURL& url,
base::Time expected_response_time,
const std::vector<char>& data);
......
......@@ -103,6 +103,13 @@ void ChildProcess::ReleaseProcess() {
main_thread_->OnProcessFinalRelease();
}
#if defined(OS_LINUX)
void ChildProcess::SetIOThreadPriority(
base::ThreadPriority io_thread_priority) {
main_thread_->SetThreadPriority(io_thread_.GetThreadId(), io_thread_priority);
}
#endif
ChildProcess* ChildProcess::current() {
return g_lazy_tls.Pointer()->Get();
}
......
......@@ -69,6 +69,10 @@ class CONTENT_EXPORT ChildProcess {
void AddRefProcess();
void ReleaseProcess();
#if defined(OS_LINUX)
void SetIOThreadPriority(base::ThreadPriority io_thread_priority);
#endif
// Getter for the one ChildProcess object for this process. Can only be called
// on the main thread.
static ChildProcess* current();
......
......@@ -749,6 +749,13 @@ std::unique_ptr<base::SharedMemory> ChildThreadImpl::AllocateSharedMemory(
return shared_buf;
}
#if defined(OS_LINUX)
void ChildThreadImpl::SetThreadPriority(base::PlatformThreadId id,
base::ThreadPriority priority) {
Send(new ChildProcessHostMsg_SetThreadPriority(id, priority));
}
#endif
bool ChildThreadImpl::OnMessageReceived(const IPC::Message& msg) {
// Resource responses are sent to the resource dispatcher.
if (resource_dispatcher_->OnMessageReceived(msg))
......
......@@ -128,6 +128,11 @@ class CONTENT_EXPORT ChildThreadImpl
IPC::Sender* sender,
bool* out_of_memory);
#if defined(OS_LINUX)
void SetThreadPriority(base::PlatformThreadId id,
base::ThreadPriority priority);
#endif
ChildSharedBitmapManager* shared_bitmap_manager() const {
return shared_bitmap_manager_.get();
}
......
......@@ -27,6 +27,10 @@
#include "ui/gfx/ipc/gfx_param_traits.h"
#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
#if defined(OS_LINUX)
#include "base/threading/platform_thread.h"
#endif
IPC_ENUM_TRAITS_MAX_VALUE(tracked_objects::ThreadData::Status,
tracked_objects::ThreadData::STATUS_LAST)
......@@ -66,6 +70,11 @@ IPC_STRUCT_TRAITS_BEGIN(tracked_objects::ProcessDataSnapshot)
IPC_STRUCT_TRAITS_MEMBER(process_id)
IPC_STRUCT_TRAITS_END()
#if defined(OS_LINUX)
IPC_ENUM_TRAITS_MAX_VALUE(base::ThreadPriority,
base::ThreadPriority::REALTIME_AUDIO)
#endif
#undef IPC_MESSAGE_EXPORT
#define IPC_MESSAGE_EXPORT CONTENT_EXPORT
......@@ -211,3 +220,10 @@ IPC_SYNC_MESSAGE_CONTROL2_1(
// memory.
IPC_MESSAGE_CONTROL1(ChildProcessHostMsg_DeletedDiscardableSharedMemory,
content::DiscardableSharedMemoryId)
#if defined(OS_LINUX)
// Asks the browser to change the priority of thread.
IPC_MESSAGE_CONTROL2(ChildProcessHostMsg_SetThreadPriority,
base::PlatformThreadId,
base::ThreadPriority)
#endif
......@@ -69,6 +69,10 @@ class CONTENT_EXPORT CategorizedWorkerPool : public base::TaskRunner,
// Create a new sequenced task graph runner.
scoped_refptr<base::SequencedTaskRunner> CreateSequencedTaskRunner();
base::PlatformThreadId background_worker_thread_id() const {
return threads_.back()->tid();
}
protected:
~CategorizedWorkerPool() override;
......
......@@ -858,6 +858,13 @@ void RenderThreadImpl::Init(
time_zone_monitor->AddClient(
time_zone_monitor_binding_.CreateInterfacePtrAndBind());
#if defined(OS_LINUX)
ChildProcess::current()->SetIOThreadPriority(base::ThreadPriority::DISPLAY);
ChildThreadImpl::current()->SetThreadPriority(
categorized_worker_pool_->background_worker_thread_id(),
base::ThreadPriority::BACKGROUND);
#endif
is_renderer_suspended_ = false;
}
......@@ -1134,6 +1141,10 @@ void RenderThreadImpl::InitializeCompositorThread() {
compositor_task_runner_->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&ThreadRestrictions::SetIOAllowed), false));
#if defined(OS_LINUX)
ChildThreadImpl::current()->SetThreadPriority(compositor_thread_->threadId(),
base::ThreadPriority::DISPLAY);
#endif
SynchronousInputHandlerProxyClient* synchronous_input_handler_proxy_client =
nullptr;
......
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