| Index: content/browser/worker_host/worker_service.cc
|
| ===================================================================
|
| --- content/browser/worker_host/worker_service.cc (revision 115981)
|
| +++ content/browser/worker_host/worker_service.cc (working copy)
|
| @@ -1,559 +0,0 @@
|
| -// Copyright (c) 2011 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/worker_host/worker_service.h"
|
| -
|
| -#include <string>
|
| -
|
| -#include "base/command_line.h"
|
| -#include "base/logging.h"
|
| -#include "base/sys_info.h"
|
| -#include "base/threading/thread.h"
|
| -#include "content/browser/resource_context.h"
|
| -#include "content/browser/worker_host/worker_message_filter.h"
|
| -#include "content/browser/worker_host/worker_process_host.h"
|
| -#include "content/browser/worker_host/worker_service_observer.h"
|
| -#include "content/common/view_messages.h"
|
| -#include "content/common/worker_messages.h"
|
| -#include "content/public/common/content_switches.h"
|
| -#include "content/public/common/process_type.h"
|
| -#include "net/base/registry_controlled_domain.h"
|
| -
|
| -using content::BrowserThread;
|
| -
|
| -const int WorkerService::kMaxWorkerProcessesWhenSharing = 10;
|
| -const int WorkerService::kMaxWorkersWhenSeparate = 64;
|
| -const int WorkerService::kMaxWorkersPerTabWhenSeparate = 16;
|
| -
|
| -WorkerService* WorkerService::GetInstance() {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - return Singleton<WorkerService>::get();
|
| -}
|
| -
|
| -WorkerService::WorkerService() : next_worker_route_id_(0) {
|
| -}
|
| -
|
| -WorkerService::~WorkerService() {
|
| - // The observers in observers_ can't be used here because they might be
|
| - // gone already.
|
| -}
|
| -
|
| -void WorkerService::OnWorkerMessageFilterClosing(WorkerMessageFilter* filter) {
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - worker->FilterShutdown(filter);
|
| - }
|
| -
|
| - // See if that process had any queued workers.
|
| - for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
|
| - i != queued_workers_.end();) {
|
| - i->RemoveFilters(filter);
|
| - if (i->NumFilters() == 0) {
|
| - i = queued_workers_.erase(i);
|
| - } else {
|
| - ++i;
|
| - }
|
| - }
|
| -
|
| - // Also, see if that process had any pending shared workers.
|
| - for (WorkerProcessHost::Instances::iterator iter =
|
| - pending_shared_workers_.begin();
|
| - iter != pending_shared_workers_.end(); ) {
|
| - iter->worker_document_set()->RemoveAll(filter);
|
| - if (iter->worker_document_set()->IsEmpty()) {
|
| - iter = pending_shared_workers_.erase(iter);
|
| - } else {
|
| - ++iter;
|
| - }
|
| - }
|
| -
|
| - // Either a worker proceess has shut down, in which case we can start one of
|
| - // the queued workers, or a renderer has shut down, in which case it doesn't
|
| - // affect anything. We call this function in both scenarios because then we
|
| - // don't have to keep track which filters are from worker processes.
|
| - TryStartingQueuedWorker();
|
| -}
|
| -
|
| -void WorkerService::CreateWorker(
|
| - const ViewHostMsg_CreateWorker_Params& params,
|
| - int route_id,
|
| - WorkerMessageFilter* filter,
|
| - const content::ResourceContext& resource_context) {
|
| - // Generate a unique route id for the browser-worker communication that's
|
| - // unique among all worker processes. That way when the worker process sends
|
| - // a wrapped IPC message through us, we know which WorkerProcessHost to give
|
| - // it to.
|
| - WorkerProcessHost::WorkerInstance instance(
|
| - params.url,
|
| - params.name,
|
| - next_worker_route_id(),
|
| - 0,
|
| - params.script_resource_appcache_id,
|
| - &resource_context);
|
| - instance.AddFilter(filter, route_id);
|
| - instance.worker_document_set()->Add(
|
| - filter, params.document_id, filter->render_process_id(),
|
| - params.render_view_route_id);
|
| -
|
| - CreateWorkerFromInstance(instance);
|
| -}
|
| -
|
| -void WorkerService::LookupSharedWorker(
|
| - const ViewHostMsg_CreateWorker_Params& params,
|
| - int route_id,
|
| - WorkerMessageFilter* filter,
|
| - const content::ResourceContext* resource_context,
|
| - bool* exists,
|
| - bool* url_mismatch) {
|
| - *exists = true;
|
| - WorkerProcessHost::WorkerInstance* instance = FindSharedWorkerInstance(
|
| - params.url, params.name, resource_context);
|
| -
|
| - if (!instance) {
|
| - // If no worker instance currently exists, we need to create a pending
|
| - // instance - this is to make sure that any subsequent lookups passing a
|
| - // mismatched URL get the appropriate url_mismatch error at lookup time.
|
| - // Having named shared workers was a Really Bad Idea due to details like
|
| - // this.
|
| - instance = CreatePendingInstance(params.url, params.name, resource_context);
|
| - *exists = false;
|
| - }
|
| -
|
| - // Make sure the passed-in instance matches the URL - if not, return an
|
| - // error.
|
| - if (params.url != instance->url()) {
|
| - *url_mismatch = true;
|
| - *exists = false;
|
| - } else {
|
| - *url_mismatch = false;
|
| - // Add our route ID to the existing instance so we can send messages to it.
|
| - instance->AddFilter(filter, route_id);
|
| -
|
| - // Add the passed filter/document_id to the worker instance.
|
| - // TODO(atwilson): This won't work if the message is from a worker process.
|
| - // We don't support that yet though (this message is only sent from
|
| - // renderers) but when we do, we'll need to add code to pass in the current
|
| - // worker's document set for nested workers.
|
| - instance->worker_document_set()->Add(
|
| - filter, params.document_id, filter->render_process_id(),
|
| - params.render_view_route_id);
|
| - }
|
| -}
|
| -
|
| -void WorkerService::CancelCreateDedicatedWorker(
|
| - int route_id,
|
| - WorkerMessageFilter* filter) {
|
| -
|
| - NOTREACHED();
|
| -}
|
| -
|
| -void WorkerService::ForwardToWorker(const IPC::Message& message,
|
| - WorkerMessageFilter* filter) {
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - if (worker->FilterMessage(message, filter))
|
| - return;
|
| - }
|
| -
|
| - // TODO(jabdelmalek): tell filter that callee is gone
|
| -}
|
| -
|
| -void WorkerService::DocumentDetached(unsigned long long document_id,
|
| - WorkerMessageFilter* filter) {
|
| - // Any associated shared workers can be shut down.
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - worker->DocumentDetached(filter, document_id);
|
| - }
|
| -
|
| - // Remove any queued shared workers for this document.
|
| - for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
|
| - iter != queued_workers_.end();) {
|
| -
|
| - iter->worker_document_set()->Remove(filter, document_id);
|
| - if (iter->worker_document_set()->IsEmpty()) {
|
| - iter = queued_workers_.erase(iter);
|
| - continue;
|
| - }
|
| - ++iter;
|
| - }
|
| -
|
| - // Remove the document from any pending shared workers.
|
| - for (WorkerProcessHost::Instances::iterator iter =
|
| - pending_shared_workers_.begin();
|
| - iter != pending_shared_workers_.end(); ) {
|
| - iter->worker_document_set()->Remove(filter, document_id);
|
| - if (iter->worker_document_set()->IsEmpty()) {
|
| - iter = pending_shared_workers_.erase(iter);
|
| - } else {
|
| - ++iter;
|
| - }
|
| - }
|
| -}
|
| -
|
| -bool WorkerService::CreateWorkerFromInstance(
|
| - WorkerProcessHost::WorkerInstance instance) {
|
| - // TODO(michaeln): We need to ensure that a process is working
|
| - // on behalf of a single browser context. The process sharing logic below
|
| - // does not ensure that. Consider making WorkerService a per browser context
|
| - // object to help with this.
|
| - WorkerProcessHost* worker = NULL;
|
| - if (CommandLine::ForCurrentProcess()->HasSwitch(
|
| - switches::kWebWorkerProcessPerCore)) {
|
| - worker = GetProcessToFillUpCores();
|
| - } else if (CommandLine::ForCurrentProcess()->HasSwitch(
|
| - switches::kWebWorkerShareProcesses)) {
|
| - worker = GetProcessForDomain(instance.url());
|
| - } else { // One process per worker.
|
| - if (!CanCreateWorkerProcess(instance)) {
|
| - queued_workers_.push_back(instance);
|
| - return true;
|
| - }
|
| - }
|
| -
|
| - // Check to see if this shared worker is already running (two pages may have
|
| - // tried to start up the worker simultaneously).
|
| - // See if a worker with this name already exists.
|
| - WorkerProcessHost::WorkerInstance* existing_instance =
|
| - FindSharedWorkerInstance(
|
| - instance.url(), instance.name(), instance.resource_context());
|
| - WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
|
| - instance.GetFilter();
|
| - // If this worker is already running, no need to create a new copy. Just
|
| - // inform the caller that the worker has been created.
|
| - if (existing_instance) {
|
| - // Walk the worker's filter list to see if this client is listed. If not,
|
| - // then it means that the worker started by the client already exited so
|
| - // we should not attach to this new one (http://crbug.com/29243).
|
| - if (!existing_instance->HasFilter(filter_info.first, filter_info.second))
|
| - return false;
|
| - filter_info.first->Send(new ViewMsg_WorkerCreated(filter_info.second));
|
| - return true;
|
| - }
|
| -
|
| - // Look to see if there's a pending instance.
|
| - WorkerProcessHost::WorkerInstance* pending = FindPendingInstance(
|
| - instance.url(), instance.name(), instance.resource_context());
|
| - // If there's no instance *and* no pending instance (or there is a pending
|
| - // instance but it does not contain our filter info), then it means the
|
| - // worker started up and exited already. Log a warning because this should
|
| - // be a very rare occurrence and is probably a bug, but it *can* happen so
|
| - // handle it gracefully.
|
| - if (!pending ||
|
| - !pending->HasFilter(filter_info.first, filter_info.second)) {
|
| - DLOG(WARNING) << "Pending worker already exited";
|
| - return false;
|
| - }
|
| -
|
| - // Assign the accumulated document set and filter list for this pending
|
| - // worker to the new instance.
|
| - DCHECK(!pending->worker_document_set()->IsEmpty());
|
| - instance.ShareDocumentSet(*pending);
|
| - for (WorkerProcessHost::WorkerInstance::FilterList::const_iterator i =
|
| - pending->filters().begin();
|
| - i != pending->filters().end(); ++i) {
|
| - instance.AddFilter(i->first, i->second);
|
| - }
|
| - RemovePendingInstances(
|
| - instance.url(), instance.name(), instance.resource_context());
|
| -
|
| - // Remove any queued instances of this worker and copy over the filter to
|
| - // this instance.
|
| - for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
|
| - iter != queued_workers_.end();) {
|
| - if (iter->Matches(instance.url(), instance.name(),
|
| - instance.resource_context())) {
|
| - DCHECK(iter->NumFilters() == 1);
|
| - WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
|
| - iter->GetFilter();
|
| - instance.AddFilter(filter_info.first, filter_info.second);
|
| - iter = queued_workers_.erase(iter);
|
| - } else {
|
| - ++iter;
|
| - }
|
| - }
|
| -
|
| - if (!worker) {
|
| - WorkerMessageFilter* first_filter = instance.filters().begin()->first;
|
| - worker = new WorkerProcessHost(
|
| - instance.resource_context(),
|
| - first_filter->resource_dispatcher_host());
|
| - // TODO(atwilson): This won't work if the message is from a worker process.
|
| - // We don't support that yet though (this message is only sent from
|
| - // renderers) but when we do, we'll need to add code to pass in the current
|
| - // worker's document set for nested workers.
|
| - if (!worker->Init(first_filter->render_process_id())) {
|
| - delete worker;
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - // TODO(michaeln): As written, test can fail per my earlier comment in
|
| - // this method, but that's a bug.
|
| - // DCHECK(worker->request_context() == instance.request_context());
|
| -
|
| - worker->CreateWorker(instance);
|
| - FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
|
| - WorkerCreated(worker, instance));
|
| - return true;
|
| -}
|
| -
|
| -WorkerProcessHost* WorkerService::GetProcessForDomain(const GURL& url) {
|
| - int num_processes = 0;
|
| - std::string domain =
|
| - net::RegistryControlledDomainService::GetDomainAndRegistry(url);
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - num_processes++;
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - for (WorkerProcessHost::Instances::const_iterator instance =
|
| - worker->instances().begin();
|
| - instance != worker->instances().end(); ++instance) {
|
| - if (net::RegistryControlledDomainService::GetDomainAndRegistry(
|
| - instance->url()) == domain) {
|
| - return worker;
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (num_processes >= kMaxWorkerProcessesWhenSharing)
|
| - return GetLeastLoadedWorker();
|
| -
|
| - return NULL;
|
| -}
|
| -
|
| -WorkerProcessHost* WorkerService::GetProcessToFillUpCores() {
|
| - int num_processes = 0;
|
| - BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - for (; !iter.Done(); ++iter)
|
| - num_processes++;
|
| -
|
| - if (num_processes >= base::SysInfo::NumberOfProcessors())
|
| - return GetLeastLoadedWorker();
|
| -
|
| - return NULL;
|
| -}
|
| -
|
| -WorkerProcessHost* WorkerService::GetLeastLoadedWorker() {
|
| - WorkerProcessHost* smallest = NULL;
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - if (!smallest || worker->instances().size() < smallest->instances().size())
|
| - smallest = worker;
|
| - }
|
| -
|
| - return smallest;
|
| -}
|
| -
|
| -bool WorkerService::CanCreateWorkerProcess(
|
| - const WorkerProcessHost::WorkerInstance& instance) {
|
| - // Worker can be fired off if *any* parent has room.
|
| - const WorkerDocumentSet::DocumentInfoSet& parents =
|
| - instance.worker_document_set()->documents();
|
| -
|
| - for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
|
| - parents.begin();
|
| - parent_iter != parents.end(); ++parent_iter) {
|
| - bool hit_total_worker_limit = false;
|
| - if (TabCanCreateWorkerProcess(parent_iter->render_process_id(),
|
| - parent_iter->render_view_id(),
|
| - &hit_total_worker_limit)) {
|
| - return true;
|
| - }
|
| - // Return false if already at the global worker limit (no need to continue
|
| - // checking parent tabs).
|
| - if (hit_total_worker_limit)
|
| - return false;
|
| - }
|
| - // If we've reached here, none of the parent tabs is allowed to create an
|
| - // instance.
|
| - return false;
|
| -}
|
| -
|
| -bool WorkerService::TabCanCreateWorkerProcess(int render_process_id,
|
| - int render_view_id,
|
| - bool* hit_total_worker_limit) {
|
| - int total_workers = 0;
|
| - int workers_per_tab = 0;
|
| - *hit_total_worker_limit = false;
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - for (WorkerProcessHost::Instances::const_iterator cur_instance =
|
| - worker->instances().begin();
|
| - cur_instance != worker->instances().end(); ++cur_instance) {
|
| - total_workers++;
|
| - if (total_workers >= kMaxWorkersWhenSeparate) {
|
| - *hit_total_worker_limit = true;
|
| - return false;
|
| - }
|
| - if (cur_instance->RendererIsParent(render_process_id, render_view_id)) {
|
| - workers_per_tab++;
|
| - if (workers_per_tab >= kMaxWorkersPerTabWhenSeparate)
|
| - return false;
|
| - }
|
| - }
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -void WorkerService::TryStartingQueuedWorker() {
|
| - if (queued_workers_.empty())
|
| - return;
|
| -
|
| - for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
|
| - i != queued_workers_.end();) {
|
| - if (CanCreateWorkerProcess(*i)) {
|
| - WorkerProcessHost::WorkerInstance instance = *i;
|
| - queued_workers_.erase(i);
|
| - CreateWorkerFromInstance(instance);
|
| -
|
| - // CreateWorkerFromInstance can modify the queued_workers_ list when it
|
| - // coalesces queued instances after starting a shared worker, so we
|
| - // have to rescan the list from the beginning (our iterator is now
|
| - // invalid). This is not a big deal as having any queued workers will be
|
| - // rare in practice so the list will be small.
|
| - i = queued_workers_.begin();
|
| - } else {
|
| - ++i;
|
| - }
|
| - }
|
| -}
|
| -
|
| -bool WorkerService::GetRendererForWorker(int worker_process_id,
|
| - int* render_process_id,
|
| - int* render_view_id) const {
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - if (iter->id() != worker_process_id)
|
| - continue;
|
| -
|
| - // This code assumes one worker per process, see function comment in header!
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - WorkerProcessHost::Instances::const_iterator first_instance =
|
| - worker->instances().begin();
|
| - if (first_instance == worker->instances().end())
|
| - return false;
|
| -
|
| - WorkerDocumentSet::DocumentInfoSet::const_iterator info =
|
| - first_instance->worker_document_set()->documents().begin();
|
| - *render_process_id = info->render_process_id();
|
| - *render_view_id = info->render_view_id();
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -const WorkerProcessHost::WorkerInstance* WorkerService::FindWorkerInstance(
|
| - int worker_process_id) {
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - if (iter->id() != worker_process_id)
|
| - continue;
|
| -
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - WorkerProcessHost::Instances::const_iterator instance =
|
| - worker->instances().begin();
|
| - return instance == worker->instances().end() ? NULL : &*instance;
|
| - }
|
| - return NULL;
|
| -}
|
| -
|
| -void WorkerService::AddObserver(WorkerServiceObserver* observer) {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - observers_.AddObserver(observer);
|
| -}
|
| -
|
| -void WorkerService::RemoveObserver(WorkerServiceObserver* observer) {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - observers_.RemoveObserver(observer);
|
| -}
|
| -
|
| -void WorkerService::NotifyWorkerDestroyed(
|
| - WorkerProcessHost* process,
|
| - int worker_route_id) {
|
| - FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
|
| - WorkerDestroyed(process, worker_route_id));
|
| -}
|
| -
|
| -void WorkerService::NotifyWorkerContextStarted(WorkerProcessHost* process,
|
| - int worker_route_id) {
|
| - FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
|
| - WorkerContextStarted(process, worker_route_id));
|
| -}
|
| -
|
| -WorkerProcessHost::WorkerInstance*
|
| -WorkerService::FindSharedWorkerInstance(
|
| - const GURL& url,
|
| - const string16& name,
|
| - const content::ResourceContext* resource_context) {
|
| - for (BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
|
| - !iter.Done(); ++iter) {
|
| - WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
|
| - for (WorkerProcessHost::Instances::iterator instance_iter =
|
| - worker->mutable_instances().begin();
|
| - instance_iter != worker->mutable_instances().end();
|
| - ++instance_iter) {
|
| - if (instance_iter->Matches(url, name, resource_context))
|
| - return &(*instance_iter);
|
| - }
|
| - }
|
| - return NULL;
|
| -}
|
| -
|
| -WorkerProcessHost::WorkerInstance*
|
| -WorkerService::FindPendingInstance(
|
| - const GURL& url,
|
| - const string16& name,
|
| - const content::ResourceContext* resource_context) {
|
| - // Walk the pending instances looking for a matching pending worker.
|
| - for (WorkerProcessHost::Instances::iterator iter =
|
| - pending_shared_workers_.begin();
|
| - iter != pending_shared_workers_.end();
|
| - ++iter) {
|
| - if (iter->Matches(url, name, resource_context)) {
|
| - return &(*iter);
|
| - }
|
| - }
|
| - return NULL;
|
| -}
|
| -
|
| -
|
| -void WorkerService::RemovePendingInstances(
|
| - const GURL& url,
|
| - const string16& name,
|
| - const content::ResourceContext* resource_context) {
|
| - // Walk the pending instances looking for a matching pending worker.
|
| - for (WorkerProcessHost::Instances::iterator iter =
|
| - pending_shared_workers_.begin();
|
| - iter != pending_shared_workers_.end(); ) {
|
| - if (iter->Matches(url, name, resource_context)) {
|
| - iter = pending_shared_workers_.erase(iter);
|
| - } else {
|
| - ++iter;
|
| - }
|
| - }
|
| -}
|
| -
|
| -WorkerProcessHost::WorkerInstance*
|
| -WorkerService::CreatePendingInstance(
|
| - const GURL& url,
|
| - const string16& name,
|
| - const content::ResourceContext* resource_context) {
|
| - // Look for an existing pending shared worker.
|
| - WorkerProcessHost::WorkerInstance* instance =
|
| - FindPendingInstance(url, name, resource_context);
|
| - if (instance)
|
| - return instance;
|
| -
|
| - // No existing pending worker - create a new one.
|
| - WorkerProcessHost::WorkerInstance pending(url, true, name, resource_context);
|
| - pending_shared_workers_.push_back(pending);
|
| - return &pending_shared_workers_.back();
|
| -}
|
|
|