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