blob: ef13976eba47295cdcd4e8007a3c6a84dc5fcfcc [file] [log] [blame]
// Copyright 2014 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.
#include "content/browser/service_worker/service_worker_process_manager.h"
#include <stddef.h>
#include <algorithm>
#include <utility>
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/site_instance.h"
#include "content/public/common/child_process_host.h"
#include "url/gurl.h"
namespace content {
namespace {
// Functor to sort by the .second element of a struct.
struct SecondGreater {
template <typename Value>
bool operator()(const Value& lhs, const Value& rhs) {
return lhs.second > rhs.second;
}
};
} // namespace
ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(
const scoped_refptr<SiteInstance>& site_instance)
: site_instance(site_instance),
process_id(site_instance->GetProcess()->GetID()) {
}
ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(int process_id)
: process_id(process_id) {
}
ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(
const ProcessInfo& other) = default;
ServiceWorkerProcessManager::ProcessInfo::~ProcessInfo() {
}
ServiceWorkerProcessManager::ServiceWorkerProcessManager(
BrowserContext* browser_context)
: browser_context_(browser_context),
process_id_for_test_(ChildProcessHost::kInvalidUniqueID),
new_process_id_for_test_(ChildProcessHost::kInvalidUniqueID),
weak_this_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
weak_this_ = weak_this_factory_.GetWeakPtr();
}
ServiceWorkerProcessManager::~ServiceWorkerProcessManager() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(IsShutdown())
<< "Call Shutdown() before destroying |this|, so that racing method "
<< "invocations don't use a destroyed BrowserContext.";
// TODO(horo): Remove after collecting crash data.
// Temporary checks to verify that ServiceWorkerProcessManager doesn't prevent
// render process hosts from shutting down: crbug.com/639193
CHECK(instance_info_.empty());
}
BrowserContext* ServiceWorkerProcessManager::browser_context() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// This is safe because reading |browser_context_| on the UI thread doesn't
// need locking (while modifying does).
return browser_context_;
}
void ServiceWorkerProcessManager::Shutdown() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
{
base::AutoLock lock(browser_context_lock_);
browser_context_ = nullptr;
}
// In single-process mode, Shutdown() is called when deleting the default
// browser context, which is itself destroyed after the RenderProcessHost,
// and RenderProcessHost::FromID() just returns a nullptr.
// The refcount decrement can be skipped anyway since there's only one process
if (!RenderProcessHost::run_renderer_in_process()) {
for (std::map<int, ProcessInfo>::const_iterator it = instance_info_.begin();
it != instance_info_.end(); ++it) {
RenderProcessHost::FromID(it->second.process_id)
->DecrementKeepAliveRefCount();
}
}
instance_info_.clear();
}
bool ServiceWorkerProcessManager::IsShutdown() {
base::AutoLock lock(browser_context_lock_);
return !browser_context_;
}
void ServiceWorkerProcessManager::AddProcessReferenceToPattern(
const GURL& pattern, int process_id) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&ServiceWorkerProcessManager::AddProcessReferenceToPattern,
weak_this_, pattern, process_id));
return;
}
ProcessRefMap& process_refs = pattern_processes_[pattern];
++process_refs[process_id];
}
void ServiceWorkerProcessManager::RemoveProcessReferenceFromPattern(
const GURL& pattern, int process_id) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&ServiceWorkerProcessManager::RemoveProcessReferenceFromPattern,
weak_this_, pattern, process_id));
return;
}
PatternProcessRefMap::iterator it = pattern_processes_.find(pattern);
if (it == pattern_processes_.end()) {
NOTREACHED() << "process references not found for pattern: " << pattern;
return;
}
ProcessRefMap& process_refs = it->second;
ProcessRefMap::iterator found = process_refs.find(process_id);
if (found == process_refs.end()) {
NOTREACHED() << "Releasing unknown process ref " << process_id;
return;
}
if (--found->second == 0) {
process_refs.erase(found);
if (process_refs.empty())
pattern_processes_.erase(it);
}
}
bool ServiceWorkerProcessManager::PatternHasProcessToRun(
const GURL& pattern) const {
PatternProcessRefMap::const_iterator it = pattern_processes_.find(pattern);
if (it == pattern_processes_.end())
return false;
return !it->second.empty();
}
ServiceWorkerStatusCode ServiceWorkerProcessManager::AllocateWorkerProcess(
int embedded_worker_id,
const GURL& pattern,
const GURL& script_url,
bool can_use_existing_process,
AllocatedProcessInfo* out_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
out_info->process_id = ChildProcessHost::kInvalidUniqueID;
out_info->start_situation = ServiceWorkerMetrics::StartSituation::UNKNOWN;
if (process_id_for_test_ != ChildProcessHost::kInvalidUniqueID) {
// Let tests specify the returned process ID. Note: We may need to be able
// to specify the error code too.
int result = can_use_existing_process ? process_id_for_test_
: new_process_id_for_test_;
out_info->process_id = result;
out_info->start_situation =
ServiceWorkerMetrics::StartSituation::EXISTING_READY_PROCESS;
return SERVICE_WORKER_OK;
}
if (IsShutdown()) {
return SERVICE_WORKER_ERROR_ABORT;
}
DCHECK(!base::ContainsKey(instance_info_, embedded_worker_id))
<< embedded_worker_id << " already has a process allocated";
if (can_use_existing_process) {
int process_id = FindAvailableProcess(pattern);
if (process_id != ChildProcessHost::kInvalidUniqueID) {
RenderProcessHost::FromID(process_id)->IncrementKeepAliveRefCount();
instance_info_.insert(
std::make_pair(embedded_worker_id, ProcessInfo(process_id)));
out_info->process_id = process_id;
out_info->start_situation =
ServiceWorkerMetrics::StartSituation::EXISTING_READY_PROCESS;
return SERVICE_WORKER_OK;
}
}
// ServiceWorkerProcessManager does not know of any renderer processes that
// are available for |pattern|. Create a SiteInstance and ask for a renderer
// process. Attempt to reuse an existing process if possible.
// TODO(clamy): Update the process reuse mechanism above following the
// implementation of
// SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE.
scoped_refptr<SiteInstanceImpl> site_instance =
SiteInstanceImpl::CreateForURL(browser_context_, script_url);
site_instance->set_is_for_service_worker();
DCHECK(site_instance->process_reuse_policy() ==
SiteInstanceImpl::ProcessReusePolicy::DEFAULT ||
site_instance->process_reuse_policy() ==
SiteInstanceImpl::ProcessReusePolicy::PROCESS_PER_SITE);
if (can_use_existing_process) {
site_instance->set_process_reuse_policy(
SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE);
}
RenderProcessHost* rph = site_instance->GetProcess();
ServiceWorkerMetrics::StartSituation start_situation;
if (!rph->HasConnection()) {
// HasConnection() is false means that Init() has not been called or the
// process has been killed.
start_situation = ServiceWorkerMetrics::StartSituation::NEW_PROCESS;
} else if (!rph->IsReady()) {
start_situation =
ServiceWorkerMetrics::StartSituation::EXISTING_UNREADY_PROCESS;
} else {
start_situation =
ServiceWorkerMetrics::StartSituation::EXISTING_READY_PROCESS;
}
// This Init() call posts a task to the IO thread that adds the RPH's
// ServiceWorkerDispatcherHost to the
// EmbeddedWorkerRegistry::process_sender_map_.
if (!rph->Init()) {
LOG(ERROR) << "Couldn't start a new process!";
return SERVICE_WORKER_ERROR_PROCESS_NOT_FOUND;
}
instance_info_.insert(
std::make_pair(embedded_worker_id, ProcessInfo(site_instance)));
rph->IncrementKeepAliveRefCount();
out_info->process_id = rph->GetID();
out_info->start_situation = start_situation;
return SERVICE_WORKER_OK;
}
void ServiceWorkerProcessManager::ReleaseWorkerProcess(int embedded_worker_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (process_id_for_test_ != ChildProcessHost::kInvalidUniqueID) {
// Unittests don't increment or decrement the worker refcount of a
// RenderProcessHost.
return;
}
if (IsShutdown()) {
// Shutdown already released all instances.
DCHECK(instance_info_.empty());
return;
}
std::map<int, ProcessInfo>::iterator info =
instance_info_.find(embedded_worker_id);
// ReleaseWorkerProcess could be called for a nonexistent worker id, for
// example, when request to start a worker is aborted on the IO thread during
// process allocation that is failed on the UI thread.
if (info == instance_info_.end())
return;
RenderProcessHost* rph = nullptr;
if (info->second.site_instance.get()) {
rph = info->second.site_instance->GetProcess();
DCHECK_EQ(info->second.process_id, rph->GetID())
<< "A SiteInstance's process shouldn't get destroyed while we're "
"holding a reference to it. Was the reference actually held?";
} else {
rph = RenderProcessHost::FromID(info->second.process_id);
DCHECK(rph)
<< "Process " << info->second.process_id
<< " was destroyed unexpectedly. Did we actually hold a reference?";
}
rph->DecrementKeepAliveRefCount();
instance_info_.erase(info);
}
std::vector<int> ServiceWorkerProcessManager::SortProcessesForPattern(
const GURL& pattern) const {
PatternProcessRefMap::const_iterator it = pattern_processes_.find(pattern);
if (it == pattern_processes_.end())
return std::vector<int>();
// Prioritize higher refcount processes to choose the process which has more
// tabs and is less likely to be backgrounded by user action like tab close.
std::vector<std::pair<int, int> > counted(
it->second.begin(), it->second.end());
std::sort(counted.begin(), counted.end(), SecondGreater());
std::vector<int> result(counted.size());
for (size_t i = 0; i < counted.size(); ++i)
result[i] = counted[i].first;
return result;
}
int ServiceWorkerProcessManager::FindAvailableProcess(const GURL& pattern) {
RenderProcessHost* backgrounded_candidate = nullptr;
// Try to find an available foreground process.
std::vector<int> sorted_candidates = SortProcessesForPattern(pattern);
for (int process_id : sorted_candidates) {
RenderProcessHost* rph = RenderProcessHost::FromID(process_id);
if (!rph || rph->FastShutdownStarted())
continue;
// Keep a backgrounded process for a suboptimal choice.
if (rph->IsProcessBackgrounded()) {
if (!backgrounded_candidate)
backgrounded_candidate = rph;
continue;
}
return process_id;
}
// No foreground processes available; choose a backgrounded one.
if (backgrounded_candidate)
return backgrounded_candidate->GetID();
return ChildProcessHost::kInvalidUniqueID;
}
} // namespace content
namespace std {
// Destroying ServiceWorkerProcessManagers only on the UI thread allows the
// member WeakPtr to safely guard the object's lifetime when used on that
// thread.
void default_delete<content::ServiceWorkerProcessManager>::operator()(
content::ServiceWorkerProcessManager* ptr) const {
content::BrowserThread::DeleteSoon(
content::BrowserThread::UI, FROM_HERE, ptr);
}
} // namespace std