1// Copyright (c) 2012 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 "chrome/browser/profiles/profile_destroyer.h"
6
7#include "base/bind.h"
8#include "base/debug/trace_event.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop/message_loop.h"
11#include "chrome/browser/profiles/profile.h"
12#include "content/public/browser/render_process_host.h"
13
14namespace {
15
16#if defined(OS_ANDROID)
17// Set the render host waiting time to 5s on Android, that's the same
18// as an "Application Not Responding" timeout.
19const int64 kTimerDelaySeconds = 5;
20#else
21const int64 kTimerDelaySeconds = 1;
22#endif
23
24}  // namespace
25
26ProfileDestroyer::DestroyerSet* ProfileDestroyer::pending_destroyers_ = NULL;
27
28// static
29void ProfileDestroyer::DestroyProfileWhenAppropriate(Profile* const profile) {
30  TRACE_EVENT0("shutdown", "ProfileDestroyer::DestroyProfileWhenAppropriate");
31
32  DCHECK(profile);
33  profile->MaybeSendDestroyedNotification();
34
35  HostSet hosts;
36  // Testing profiles can simply be deleted directly. Some tests don't setup
37  // RenderProcessHost correctly and don't necessary run on the UI thread
38  // anyway, so we can't use the AllHostIterator.
39  if (profile->AsTestingProfile() == NULL) {
40    GetHostsForProfile(profile, &hosts);
41    if (!profile->IsOffTheRecord() && profile->HasOffTheRecordProfile())
42      GetHostsForProfile(profile->GetOffTheRecordProfile(), &hosts);
43  }
44  // Generally, !hosts.empty() means that there is a leak in a render process
45  // host that MUST BE FIXED!!!
46  //
47  // However, off-the-record profiles are destroyed before their
48  // RenderProcessHosts in order to erase private data quickly, and
49  // RenderProcessHostImpl::Release() avoids destroying RenderProcessHosts in
50  // --single-process mode to avoid race conditions.
51  DCHECK(hosts.empty() || profile->IsOffTheRecord() ||
52      content::RenderProcessHost::run_renderer_in_process()) << \
53      "Profile still has " << hosts.size() << " hosts";
54  // Note that we still test for !profile->IsOffTheRecord here even though we
55  // DCHECK'd above because we want to protect Release builds against this even
56  // we need to identify if there are leaks when we run Debug builds.
57  if (hosts.empty() || !profile->IsOffTheRecord()) {
58    if (profile->IsOffTheRecord())
59      profile->GetOriginalProfile()->DestroyOffTheRecordProfile();
60    else
61      delete profile;
62  } else {
63    // The instance will destroy itself once all render process hosts referring
64    // to it are properly terminated.
65    new ProfileDestroyer(profile, &hosts);
66  }
67}
68
69// This can be called to cancel any pending destruction and destroy the profile
70// now, e.g., if the parent profile is being destroyed while the incognito one
71// still pending...
72void ProfileDestroyer::DestroyOffTheRecordProfileNow(Profile* const profile) {
73  DCHECK(profile);
74  DCHECK(profile->IsOffTheRecord());
75  if (pending_destroyers_) {
76    for (DestroyerSet::iterator i = pending_destroyers_->begin();
77         i != pending_destroyers_->end(); ++i) {
78      if ((*i)->profile_ == profile) {
79        // We want to signal this in debug builds so that we don't lose sight of
80        // these potential leaks, but we handle it in release so that we don't
81        // crash or corrupt profile data on disk.
82        NOTREACHED() << "A render process host wasn't destroyed early enough.";
83        (*i)->profile_ = NULL;
84        break;
85      }
86    }
87  }
88  DCHECK(profile->GetOriginalProfile());
89  profile->GetOriginalProfile()->DestroyOffTheRecordProfile();
90}
91
92ProfileDestroyer::ProfileDestroyer(Profile* const profile, HostSet* hosts)
93    : timer_(false, false),
94      num_hosts_(0),
95      profile_(profile),
96      weak_ptr_factory_(this) {
97  if (pending_destroyers_ == NULL)
98    pending_destroyers_ = new DestroyerSet;
99  pending_destroyers_->insert(this);
100  for (HostSet::iterator i = hosts->begin(); i != hosts->end(); ++i) {
101    (*i)->AddObserver(this);
102    // For each of the observations, we bump up our reference count.
103    // It will go back to 0 and free us when all hosts are terminated.
104    ++num_hosts_;
105  }
106  // If we are going to wait for render process hosts, we don't want to do it
107  // for longer than kTimerDelaySeconds.
108  if (num_hosts_) {
109    timer_.Start(FROM_HERE,
110                 base::TimeDelta::FromSeconds(kTimerDelaySeconds),
111                 base::Bind(&ProfileDestroyer::DestroyProfile,
112                            weak_ptr_factory_.GetWeakPtr()));
113  }
114}
115
116ProfileDestroyer::~ProfileDestroyer() {
117  // Check again, in case other render hosts were added while we were
118  // waiting for the previous ones to go away...
119  if (profile_)
120    DestroyProfileWhenAppropriate(profile_);
121
122  // Don't wait for pending registrations, if any, these hosts are buggy.
123  // Note: this can happen, but if so, it's better to crash here than wait
124  // for the host to dereference a deleted Profile. http://crbug.com/248625
125  CHECK_EQ(0U, num_hosts_) << "Some render process hosts were not "
126                           << "destroyed early enough!";
127
128  DCHECK(pending_destroyers_ != NULL);
129  DestroyerSet::iterator iter = pending_destroyers_->find(this);
130  DCHECK(iter != pending_destroyers_->end());
131  pending_destroyers_->erase(iter);
132  if (pending_destroyers_->empty()) {
133    delete pending_destroyers_;
134    pending_destroyers_ = NULL;
135  }
136}
137
138void ProfileDestroyer::RenderProcessHostDestroyed(
139    content::RenderProcessHost* host) {
140  DCHECK(num_hosts_ > 0);
141  --num_hosts_;
142  if (num_hosts_ == 0) {
143    // Delay the destruction one step further in case other observers need to
144    // look at the profile attached to the host.
145    base::MessageLoop::current()->PostTask(
146        FROM_HERE, base::Bind(
147            &ProfileDestroyer::DestroyProfile, weak_ptr_factory_.GetWeakPtr()));
148  }
149}
150
151void ProfileDestroyer::DestroyProfile() {
152  // We might have been cancelled externally before the timer expired.
153  if (!profile_) {
154    delete this;
155    return;
156  }
157
158  DCHECK(profile_->IsOffTheRecord());
159  DCHECK(profile_->GetOriginalProfile());
160  profile_->GetOriginalProfile()->DestroyOffTheRecordProfile();
161  profile_ = NULL;
162
163  // And stop the timer so we can be released early too.
164  timer_.Stop();
165
166  delete this;
167}
168
169// static
170bool ProfileDestroyer::GetHostsForProfile(
171    Profile* const profile, HostSet* hosts) {
172  for (content::RenderProcessHost::iterator iter(
173        content::RenderProcessHost::AllHostsIterator());
174      !iter.IsAtEnd(); iter.Advance()) {
175    content::RenderProcessHost* render_process_host = iter.GetCurrentValue();
176    if (render_process_host &&
177            render_process_host->GetBrowserContext() == profile) {
178      hosts->insert(render_process_host);
179    }
180  }
181  return !hosts->empty();
182}
183