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