blob: 890941e6e97f46bd48c6703a6fc2e267c4973e5d [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// The main point of this class is to cache ARC proc nspid<->pid mapping
// globally. Since the calculation is costly, a dedicated worker thread is
// used. All read/write of its internal data structure (i.e., the mapping)
// should be on this thread.
#include "chrome/browser/chromeos/arc/arc_process_service.h"
#include <queue>
#include <set>
#include <string>
#include "base/process/process.h"
#include "base/process/process_iterator.h"
#include "base/task_runner_util.h"
#include "base/trace_event/trace_event.h"
#include "content/public/browser/browser_thread.h"
namespace arc {
namespace {
const char kSequenceToken[] = "arc_process_service";
// Weak pointer. This class is owned by ArcServiceManager.
ArcProcessService* g_arc_process_service = nullptr;
} // namespace
using base::kNullProcessId;
using base::Process;
using base::ProcessId;
using base::SequencedWorkerPool;
using std::map;
using std::set;
using std::vector;
ArcProcessService::ArcProcessService(ArcBridgeService* bridge_service)
: ArcService(bridge_service),
worker_pool_(new SequencedWorkerPool(1, "arc_process_manager")),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc_bridge_service()->AddObserver(this);
DCHECK(!g_arc_process_service);
g_arc_process_service = this;
// Not intended to be used from the creating thread.
thread_checker_.DetachFromThread();
}
ArcProcessService::~ArcProcessService() {
DCHECK(g_arc_process_service == this);
g_arc_process_service = nullptr;
arc_bridge_service()->RemoveObserver(this);
worker_pool_->Shutdown();
}
// static
ArcProcessService* ArcProcessService::Get() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return g_arc_process_service;
}
void ArcProcessService::OnProcessInstanceReady() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
worker_pool_->PostNamedSequencedWorkerTask(
kSequenceToken,
FROM_HERE,
base::Bind(&ArcProcessService::Reset,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcProcessService::Reset() {
DCHECK(thread_checker_.CalledOnValidThread());
nspid_to_pid_.clear();
}
bool ArcProcessService::RequestProcessList(
RequestProcessListCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc::ProcessInstance* process_instance =
arc_bridge_service()->process_instance();
if (!process_instance) {
return false;
}
process_instance->RequestProcessList(
base::Bind(&ArcProcessService::OnReceiveProcessList,
weak_ptr_factory_.GetWeakPtr(),
callback));
return true;
}
void ArcProcessService::OnReceiveProcessList(
const RequestProcessListCallback& callback,
mojo::Array<arc::RunningAppProcessInfoPtr> mojo_processes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto raw_processes = new vector<RunningAppProcessInfoPtr>();
mojo_processes.Swap(raw_processes);
auto ret_processes = new vector<ArcProcess>();
// Post to its dedicated worker thread to avoid race condition.
// Since no two tasks with the same token should be run at the same.
// Note: GetSequencedTaskRunner's shutdown behavior defaults to
// SKIP_ON_SHUTDOWN (ongoing task blocks shutdown).
// So in theory using Unretained(this) should be fine since the life cycle
// of |this| is the same as the main browser.
// To be safe I still use weak pointers, but weak_ptrs can only bind to
// methods without return values. That's why I can't use
// PostTaskAndReplyWithResult but handle the return object by myself.
auto runner = worker_pool_->GetSequencedTaskRunner(
worker_pool_->GetNamedSequenceToken(kSequenceToken));
runner->PostTaskAndReply(
FROM_HERE,
base::Bind(&ArcProcessService::UpdateAndReturnProcessList,
weak_ptr_factory_.GetWeakPtr(),
base::Owned(raw_processes),
base::Unretained(ret_processes)),
base::Bind(&ArcProcessService::CallbackRelay,
weak_ptr_factory_.GetWeakPtr(),
callback,
base::Owned(ret_processes)));
}
void ArcProcessService::CallbackRelay(
const RequestProcessListCallback& callback,
const vector<ArcProcess>* ret_processes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
callback.Run(*ret_processes);
}
void ArcProcessService::UpdateAndReturnProcessList(
const vector<arc::RunningAppProcessInfoPtr>* raw_processes,
vector<ArcProcess>* ret_processes) {
DCHECK(thread_checker_.CalledOnValidThread());
// Cleanup dead pids in the cache |nspid_to_pid_|.
set<ProcessId> nspid_to_remove;
for (const auto& entry : nspid_to_pid_) {
nspid_to_remove.insert(entry.first);
}
bool unmapped_nspid = false;
for (const auto& entry : *raw_processes) {
// erase() returns 0 if coudln't find the key. It means a new process.
if (nspid_to_remove.erase(entry->pid) == 0) {
nspid_to_pid_[entry->pid] = kNullProcessId;
unmapped_nspid = true;
}
}
for (const auto& entry : nspid_to_remove) {
nspid_to_pid_.erase(entry);
}
// The operation is costly so avoid calling it when possible.
if (unmapped_nspid) {
UpdateNspidToPidMap();
}
PopulateProcessList(raw_processes, ret_processes);
}
void ArcProcessService::PopulateProcessList(
const vector<arc::RunningAppProcessInfoPtr>* raw_processes,
vector<ArcProcess>* ret_processes) {
DCHECK(thread_checker_.CalledOnValidThread());
for (const auto& entry : *raw_processes) {
const auto it = nspid_to_pid_.find(entry->pid);
// In case the process already dies so couldn't find corresponding pid.
if (it != nspid_to_pid_.end() && it->second != kNullProcessId) {
ArcProcess arc_process = {
entry->pid, it->second, entry->process_name, entry->process_state};
ret_processes->push_back(arc_process);
}
}
}
// Computes a map from PID in ARC namespace to PID in system namespace.
// The returned map contains ARC processes only.
void ArcProcessService::UpdateNspidToPidMap() {
DCHECK(thread_checker_.CalledOnValidThread());
TRACE_EVENT0("browser", "ArcProcessService::UpdateNspidToPidMap");
// NB: Despite of its name, ProcessIterator::Snapshot() may return
// inconsistent information because it simply walks procfs. Especially
// we must not assume the parent-child relationships are consistent.
const base::ProcessIterator::ProcessEntries& entry_list =
base::ProcessIterator(nullptr).Snapshot();
// System may contain many different namespaces so several different
// processes may have the same nspid. We need to get the proper subset of
// processes to create correct nspid -> pid map.
// Construct the process tree.
// NB: This can contain a loop in case of race conditions.
map<ProcessId, vector<ProcessId> > process_tree;
for (const base::ProcessEntry& entry : entry_list)
process_tree[entry.parent_pid()].push_back(entry.pid());
// Find the ARC init process.
ProcessId arc_init_pid = kNullProcessId;
for (const base::ProcessEntry& entry : entry_list) {
// TODO(nya): Add more constraints to avoid mismatches.
std::string process_name =
!entry.cmd_line_args().empty() ? entry.cmd_line_args()[0] : "";
if (process_name == "/init") {
arc_init_pid = entry.pid();
break;
}
}
// Enumerate all processes under ARC init and create nspid -> pid map.
if (arc_init_pid != kNullProcessId) {
std::queue<ProcessId> queue;
std::set<ProcessId> visited;
queue.push(arc_init_pid);
while (!queue.empty()) {
ProcessId pid = queue.front();
queue.pop();
// Do not visit the same process twice. Otherwise we may enter an infinite
// loop if |process_tree| contains a loop.
if (!visited.insert(pid).second)
continue;
ProcessId nspid = base::Process(pid).GetPidInNamespace();
// All ARC processes should be in namespace so nspid is usually non-null,
// but this can happen if the process has already gone.
// Only add processes we're interested in (those appear as keys in
// |nspid_to_pid_|).
if (nspid != kNullProcessId &&
nspid_to_pid_.find(nspid) != nspid_to_pid_.end())
nspid_to_pid_[nspid] = pid;
for (ProcessId child_pid : process_tree[pid])
queue.push(child_pid);
}
}
}
} // namespace arc