1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/service_worker/service_worker_process_manager.h"
6
7#include "content/browser/renderer_host/render_process_host_impl.h"
8#include "content/browser/service_worker/service_worker_context_wrapper.h"
9#include "content/public/browser/browser_thread.h"
10#include "content/public/browser/site_instance.h"
11#include "url/gurl.h"
12
13namespace content {
14
15namespace {
16
17// Functor to sort by the .second element of a struct.
18struct SecondGreater {
19  template <typename Value>
20  bool operator()(const Value& lhs, const Value& rhs) {
21    return lhs.second > rhs.second;
22  }
23};
24
25}  // namespace
26
27static bool IncrementWorkerRefCountByPid(int process_id) {
28  RenderProcessHost* rph = RenderProcessHost::FromID(process_id);
29  if (!rph || rph->FastShutdownStarted())
30    return false;
31
32  static_cast<RenderProcessHostImpl*>(rph)->IncrementWorkerRefCount();
33  return true;
34}
35
36ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(
37    const scoped_refptr<SiteInstance>& site_instance)
38    : site_instance(site_instance),
39      process_id(site_instance->GetProcess()->GetID()) {
40}
41
42ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(int process_id)
43    : process_id(process_id) {
44}
45
46ServiceWorkerProcessManager::ProcessInfo::~ProcessInfo() {
47}
48
49ServiceWorkerProcessManager::ServiceWorkerProcessManager(
50    BrowserContext* browser_context)
51    : browser_context_(browser_context),
52      process_id_for_test_(-1),
53      weak_this_factory_(this),
54      weak_this_(weak_this_factory_.GetWeakPtr()) {
55}
56
57ServiceWorkerProcessManager::~ServiceWorkerProcessManager() {
58  DCHECK_CURRENTLY_ON(BrowserThread::UI);
59  DCHECK(browser_context_ == NULL)
60      << "Call Shutdown() before destroying |this|, so that racing method "
61      << "invocations don't use a destroyed BrowserContext.";
62}
63
64void ServiceWorkerProcessManager::Shutdown() {
65  browser_context_ = NULL;
66  for (std::map<int, ProcessInfo>::const_iterator it = instance_info_.begin();
67       it != instance_info_.end();
68       ++it) {
69    RenderProcessHost* rph = RenderProcessHost::FromID(it->second.process_id);
70    DCHECK(rph);
71    static_cast<RenderProcessHostImpl*>(rph)->DecrementWorkerRefCount();
72  }
73  instance_info_.clear();
74}
75
76void ServiceWorkerProcessManager::AddProcessReferenceToPattern(
77    const GURL& pattern, int process_id) {
78  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
79    BrowserThread::PostTask(
80        BrowserThread::UI,
81        FROM_HERE,
82        base::Bind(&ServiceWorkerProcessManager::AddProcessReferenceToPattern,
83                   weak_this_,
84                   pattern,
85                   process_id));
86    return;
87  }
88
89  ProcessRefMap& process_refs = pattern_processes_[pattern];
90  ++process_refs[process_id];
91}
92
93void ServiceWorkerProcessManager::RemoveProcessReferenceFromPattern(
94    const GURL& pattern, int process_id) {
95  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
96    BrowserThread::PostTask(
97        BrowserThread::UI,
98        FROM_HERE,
99        base::Bind(
100          &ServiceWorkerProcessManager::RemoveProcessReferenceFromPattern,
101          weak_this_,
102          pattern,
103          process_id));
104    return;
105  }
106
107  PatternProcessRefMap::iterator it = pattern_processes_.find(pattern);
108  if (it == pattern_processes_.end()) {
109    NOTREACHED() << "process refrences not found for pattern: " << pattern;
110    return;
111  }
112  ProcessRefMap& process_refs = it->second;
113  ProcessRefMap::iterator found = process_refs.find(process_id);
114  if (found == process_refs.end()) {
115    NOTREACHED() << "Releasing unknown process ref " << process_id;
116    return;
117  }
118  if (--found->second == 0) {
119    process_refs.erase(found);
120    if (process_refs.empty())
121      pattern_processes_.erase(it);
122  }
123}
124
125bool ServiceWorkerProcessManager::PatternHasProcessToRun(
126    const GURL& pattern) const {
127  PatternProcessRefMap::const_iterator it = pattern_processes_.find(pattern);
128  if (it == pattern_processes_.end())
129    return false;
130  return !it->second.empty();
131}
132
133void ServiceWorkerProcessManager::AllocateWorkerProcess(
134    int embedded_worker_id,
135    const GURL& pattern,
136    const GURL& script_url,
137    const base::Callback<void(ServiceWorkerStatusCode, int process_id)>&
138        callback) {
139  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
140    BrowserThread::PostTask(
141        BrowserThread::UI,
142        FROM_HERE,
143        base::Bind(&ServiceWorkerProcessManager::AllocateWorkerProcess,
144                   weak_this_,
145                   embedded_worker_id,
146                   pattern,
147                   script_url,
148                   callback));
149    return;
150  }
151
152  if (process_id_for_test_ != -1) {
153    // Let tests specify the returned process ID. Note: We may need to be able
154    // to specify the error code too.
155    BrowserThread::PostTask(
156        BrowserThread::IO,
157        FROM_HERE,
158        base::Bind(callback, SERVICE_WORKER_OK, process_id_for_test_));
159    return;
160  }
161
162  DCHECK(!ContainsKey(instance_info_, embedded_worker_id))
163      << embedded_worker_id << " already has a process allocated";
164
165  std::vector<int> sorted_candidates = SortProcessesForPattern(pattern);
166  for (std::vector<int>::const_iterator it = sorted_candidates.begin();
167       it != sorted_candidates.end();
168       ++it) {
169    if (!IncrementWorkerRefCountByPid(*it))
170      continue;
171    instance_info_.insert(
172        std::make_pair(embedded_worker_id, ProcessInfo(*it)));
173    BrowserThread::PostTask(BrowserThread::IO,
174                            FROM_HERE,
175                            base::Bind(callback, SERVICE_WORKER_OK, *it));
176    return;
177  }
178
179  if (!browser_context_) {
180    // Shutdown has started.
181    BrowserThread::PostTask(
182        BrowserThread::IO,
183        FROM_HERE,
184        base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED, -1));
185    return;
186  }
187  // No existing processes available; start a new one.
188  scoped_refptr<SiteInstance> site_instance =
189      SiteInstance::CreateForURL(browser_context_, script_url);
190  RenderProcessHost* rph = site_instance->GetProcess();
191  // This Init() call posts a task to the IO thread that adds the RPH's
192  // ServiceWorkerDispatcherHost to the
193  // EmbeddedWorkerRegistry::process_sender_map_.
194  if (!rph->Init()) {
195    LOG(ERROR) << "Couldn't start a new process!";
196    BrowserThread::PostTask(
197        BrowserThread::IO,
198        FROM_HERE,
199        base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED, -1));
200    return;
201  }
202
203  instance_info_.insert(
204      std::make_pair(embedded_worker_id, ProcessInfo(site_instance)));
205
206  static_cast<RenderProcessHostImpl*>(rph)->IncrementWorkerRefCount();
207  BrowserThread::PostTask(
208      BrowserThread::IO,
209      FROM_HERE,
210      base::Bind(callback, SERVICE_WORKER_OK, rph->GetID()));
211}
212
213void ServiceWorkerProcessManager::ReleaseWorkerProcess(int embedded_worker_id) {
214  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
215    BrowserThread::PostTask(
216        BrowserThread::UI,
217        FROM_HERE,
218        base::Bind(&ServiceWorkerProcessManager::ReleaseWorkerProcess,
219                   weak_this_,
220                   embedded_worker_id));
221    return;
222  }
223  if (process_id_for_test_ != -1) {
224    // Unittests don't increment or decrement the worker refcount of a
225    // RenderProcessHost.
226    return;
227  }
228  if (browser_context_ == NULL) {
229    // Shutdown already released all instances.
230    DCHECK(instance_info_.empty());
231    return;
232  }
233  std::map<int, ProcessInfo>::iterator info =
234      instance_info_.find(embedded_worker_id);
235  DCHECK(info != instance_info_.end());
236  RenderProcessHost* rph = NULL;
237  if (info->second.site_instance.get()) {
238    rph = info->second.site_instance->GetProcess();
239    DCHECK_EQ(info->second.process_id, rph->GetID())
240        << "A SiteInstance's process shouldn't get destroyed while we're "
241           "holding a reference to it. Was the reference actually held?";
242  } else {
243    rph = RenderProcessHost::FromID(info->second.process_id);
244    DCHECK(rph)
245        << "Process " << info->second.process_id
246        << " was destroyed unexpectedly. Did we actually hold a reference?";
247  }
248  static_cast<RenderProcessHostImpl*>(rph)->DecrementWorkerRefCount();
249  instance_info_.erase(info);
250}
251
252std::vector<int> ServiceWorkerProcessManager::SortProcessesForPattern(
253    const GURL& pattern) const {
254  PatternProcessRefMap::const_iterator it = pattern_processes_.find(pattern);
255  if (it == pattern_processes_.end())
256    return std::vector<int>();
257
258  std::vector<std::pair<int, int> > counted(
259      it->second.begin(), it->second.end());
260  std::sort(counted.begin(), counted.end(), SecondGreater());
261
262  std::vector<int> result(counted.size());
263  for (size_t i = 0; i < counted.size(); ++i)
264    result[i] = counted[i].first;
265  return result;
266}
267
268}  // namespace content
269
270namespace base {
271// Destroying ServiceWorkerProcessManagers only on the UI thread allows the
272// member WeakPtr to safely guard the object's lifetime when used on that
273// thread.
274void DefaultDeleter<content::ServiceWorkerProcessManager>::operator()(
275    content::ServiceWorkerProcessManager* ptr) const {
276  content::BrowserThread::DeleteSoon(
277      content::BrowserThread::UI, FROM_HERE, ptr);
278}
279}  // namespace base
280