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), weak_ptr_factory_(this) { 20 if (thread_mode == SINGLE_THREADED) 21 thread_checker_.reset(new base::ThreadChecker); 22 } 23 24 ResourceTracker::~ResourceTracker() {} 25 26 void ResourceTracker::CheckThreadingPreconditions() const { 27 DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread()); 28 #ifndef NDEBUG 29 ProxyLock::AssertAcquired(); 30 #endif 31 } 32 33 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 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 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 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 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 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 165 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 173 void ResourceTracker::UseOddResourceValueInDebugMode() { 174 #if !defined(NDEBUG) 175 DCHECK_EQ(0, last_resource_value_); 176 177 ++last_resource_value_; 178 #endif 179 } 180 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 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 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 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 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