resource_tracker.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 "ppapi/shared_impl/resource_tracker.h"
6
7#include "base/bind.h"
8#include "base/compiler_specific.h"
9#include "base/message_loop.h"
10#include "ppapi/shared_impl/callback_tracker.h"
11#include "ppapi/shared_impl/id_assignment.h"
12#include "ppapi/shared_impl/ppapi_globals.h"
13#include "ppapi/shared_impl/proxy_lock.h"
14#include "ppapi/shared_impl/resource.h"
15
16namespace ppapi {
17
18ResourceTracker::ResourceTracker(ThreadMode thread_mode)
19    : last_resource_value_(0),
20      weak_ptr_factory_(this) {
21  if (thread_mode == SINGLE_THREADED)
22    thread_checker_.reset(new base::ThreadChecker);
23}
24
25ResourceTracker::~ResourceTracker() {
26}
27
28void ResourceTracker::CheckThreadingPreconditions() const {
29  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
30#ifndef NDEBUG
31  ProxyLock::AssertAcquired();
32#endif
33}
34
35Resource* ResourceTracker::GetResource(PP_Resource res) const {
36  CheckThreadingPreconditions();
37  ResourceMap::const_iterator i = live_resources_.find(res);
38  if (i == live_resources_.end())
39    return NULL;
40  return i->second.first;
41}
42
43void ResourceTracker::AddRefResource(PP_Resource res) {
44  CheckThreadingPreconditions();
45  DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE))
46      << res << " is not a PP_Resource.";
47  ResourceMap::iterator i = live_resources_.find(res);
48  if (i == live_resources_.end())
49    return;
50
51  // Prevent overflow of refcount.
52  if (i->second.second ==
53      std::numeric_limits<ResourceAndRefCount::second_type>::max())
54    return;
55
56  // When we go from 0 to 1 plugin ref count, keep an additional "real" ref
57  // on its behalf.
58  if (i->second.second == 0)
59    i->second.first->AddRef();
60
61  i->second.second++;
62  return;
63}
64
65void ResourceTracker::ReleaseResource(PP_Resource res) {
66  CheckThreadingPreconditions();
67  DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE))
68      << res << " is not a PP_Resource.";
69  ResourceMap::iterator i = live_resources_.find(res);
70  if (i == live_resources_.end())
71    return;
72
73  // Prevent underflow of refcount.
74  if (i->second.second == 0)
75    return;
76
77  i->second.second--;
78  if (i->second.second == 0) {
79    LastPluginRefWasDeleted(i->second.first);
80
81    // When we go from 1 to 0 plugin ref count, free the additional "real" ref
82    // on its behalf. THIS WILL MOST LIKELY RELEASE THE OBJECT AND REMOVE IT
83    // FROM OUR LIST.
84    i->second.first->Release();
85  }
86}
87
88void ResourceTracker::ReleaseResourceSoon(PP_Resource res) {
89  base::MessageLoop::current()->PostNonNestableTask(
90      FROM_HERE,
91      RunWhileLocked(base::Bind(&ResourceTracker::ReleaseResource,
92                                weak_ptr_factory_.GetWeakPtr(),
93                                res)));
94}
95
96void ResourceTracker::DidCreateInstance(PP_Instance instance) {
97  CheckThreadingPreconditions();
98  // Due to the infrastructure of some tests, the instance is registered
99  // twice in a few cases. It would be nice not to do that and assert here
100  // instead.
101  if (instance_map_.find(instance) != instance_map_.end())
102    return;
103  instance_map_[instance] = linked_ptr<InstanceData>(new InstanceData);
104}
105
106void ResourceTracker::DidDeleteInstance(PP_Instance instance) {
107  CheckThreadingPreconditions();
108  InstanceMap::iterator found_instance = instance_map_.find(instance);
109
110  // Due to the infrastructure of some tests, the instance is unregistered
111  // twice in a few cases. It would be nice not to do that and assert here
112  // instead.
113  if (found_instance == instance_map_.end())
114    return;
115
116  InstanceData& data = *found_instance->second;
117
118  // Force release all plugin references to resources associated with the
119  // deleted instance. Make a copy since as we iterate through them, each one
120  // will remove itself from the tracking info individually.
121  ResourceSet to_delete = data.resources;
122  ResourceSet::iterator cur = to_delete.begin();
123  while (cur != to_delete.end()) {
124    // Note that it's remotely possible for the object to already be deleted
125    // from the live resources. One case is if a resource object is holding
126    // the last ref to another. When we release the first one, it will release
127    // the second one. So the second one will be gone when we eventually get
128    // to it.
129    ResourceMap::iterator found_resource = live_resources_.find(*cur);
130    if (found_resource != live_resources_.end()) {
131      Resource* resource = found_resource->second.first;
132      if (found_resource->second.second > 0) {
133        LastPluginRefWasDeleted(resource);
134        found_resource->second.second = 0;
135
136        // This will most likely delete the resource object and remove it
137        // from the live_resources_ list.
138        resource->Release();
139      }
140    }
141
142    cur++;
143  }
144
145  // In general the above pass will delete all the resources and there won't
146  // be any left in the map. However, if parts of the implementation are still
147  // holding on to internal refs, we need to tell them that the instance is
148  // gone.
149  to_delete = data.resources;
150  cur = to_delete.begin();
151  while (cur != to_delete.end()) {
152    ResourceMap::iterator found_resource = live_resources_.find(*cur);
153    if (found_resource != live_resources_.end())
154      found_resource->second.first->NotifyInstanceWasDeleted();
155    cur++;
156  }
157
158  instance_map_.erase(instance);
159}
160
161int ResourceTracker::GetLiveObjectsForInstance(PP_Instance instance) const {
162  CheckThreadingPreconditions();
163  InstanceMap::const_iterator found = instance_map_.find(instance);
164  if (found == instance_map_.end())
165    return 0;
166  return static_cast<int>(found->second->resources.size());
167}
168
169PP_Resource ResourceTracker::AddResource(Resource* object) {
170  CheckThreadingPreconditions();
171  // If the plugin manages to create too many resources, don't do crazy stuff.
172  if (last_resource_value_ == kMaxPPId)
173    return 0;
174
175  // Allocate an ID. Note there's a rare error condition below that means we
176  // could end up not using |new_id|, but that's harmless.
177  PP_Resource new_id = MakeTypedId(++last_resource_value_, PP_ID_TYPE_RESOURCE);
178
179  // Some objects have a 0 instance, meaning they aren't associated with any
180  // instance, so they won't be in |instance_map_|. This is (as of this writing)
181  // only true of the PPB_MessageLoop resource for the main thread.
182  if (object->pp_instance()) {
183    InstanceMap::iterator found = instance_map_.find(object->pp_instance());
184    if (found == instance_map_.end()) {
185      // If you hit this, it's likely somebody forgot to call DidCreateInstance,
186      // the resource was created with an invalid PP_Instance, or the renderer
187      // side tried to create a resource for a plugin that crashed/exited. This
188      // could happen for OOP plugins where due to reentrancies in context of
189      // outgoing sync calls the renderer can send events after a plugin has
190      // exited.
191      DLOG(INFO) << "Failed to find plugin instance in instance map";
192      return 0;
193    }
194    found->second->resources.insert(new_id);
195  }
196
197  live_resources_[new_id] = ResourceAndRefCount(object, 0);
198  return new_id;
199}
200
201void ResourceTracker::RemoveResource(Resource* object) {
202  CheckThreadingPreconditions();
203  PP_Resource pp_resource = object->pp_resource();
204  InstanceMap::iterator found = instance_map_.find(object->pp_instance());
205  if (found != instance_map_.end())
206    found->second->resources.erase(pp_resource);
207  live_resources_.erase(pp_resource);
208}
209
210void ResourceTracker::LastPluginRefWasDeleted(Resource* object) {
211  // Bug http://crbug.com/134611 indicates that sometimes the resource tracker
212  // is null here. This should never be the case since if we have a resource in
213  // the tracker, it should always have a valid instance associated with it
214  // (except for the resource for the main thread's message loop, which has
215  // instance set to 0).
216  // As a result, we do some CHECKs here to see what types of problems the
217  // instance might have before dispatching.
218  //
219  // TODO(brettw) remove these checks when this bug is no longer relevant.
220  // Note, we do an imperfect check here; this might be a loop that's not the
221  // main one.
222  const bool is_message_loop = (object->AsPPB_MessageLoop_API() != NULL);
223  CHECK(object->pp_instance() || is_message_loop);
224  CallbackTracker* callback_tracker =
225      PpapiGlobals::Get()->GetCallbackTrackerForInstance(object->pp_instance());
226  CHECK(callback_tracker || is_message_loop);
227  if (callback_tracker)
228    callback_tracker->PostAbortForResource(object->pp_resource());
229  object->NotifyLastPluginRefWasDeleted();
230}
231
232}  // namespace ppapi
233