// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ppapi/shared_impl/resource_tracker.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/message_loop/message_loop.h" #include "ppapi/shared_impl/callback_tracker.h" #include "ppapi/shared_impl/id_assignment.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/proxy_lock.h" #include "ppapi/shared_impl/resource.h" namespace ppapi { ResourceTracker::ResourceTracker(ThreadMode thread_mode) : last_resource_value_(0), weak_ptr_factory_(this) { if (thread_mode == SINGLE_THREADED) thread_checker_.reset(new base::ThreadChecker); } ResourceTracker::~ResourceTracker() {} void ResourceTracker::CheckThreadingPreconditions() const { DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread()); #ifndef NDEBUG ProxyLock::AssertAcquired(); #endif } Resource* ResourceTracker::GetResource(PP_Resource res) const { CheckThreadingPreconditions(); ResourceMap::const_iterator i = live_resources_.find(res); if (i == live_resources_.end()) return NULL; return i->second.first; } void ResourceTracker::AddRefResource(PP_Resource res) { CheckThreadingPreconditions(); DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE)) << res << " is not a PP_Resource."; DCHECK(CanOperateOnResource(res)); ResourceMap::iterator i = live_resources_.find(res); if (i == live_resources_.end()) return; // Prevent overflow of refcount. if (i->second.second == std::numeric_limits::max()) return; // When we go from 0 to 1 plugin ref count, keep an additional "real" ref // on its behalf. if (i->second.second == 0) i->second.first->AddRef(); i->second.second++; return; } void ResourceTracker::ReleaseResource(PP_Resource res) { CheckThreadingPreconditions(); DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE)) << res << " is not a PP_Resource."; DCHECK(CanOperateOnResource(res)); ResourceMap::iterator i = live_resources_.find(res); if (i == live_resources_.end()) return; // Prevent underflow of refcount. if (i->second.second == 0) return; i->second.second--; if (i->second.second == 0) { LastPluginRefWasDeleted(i->second.first); // When we go from 1 to 0 plugin ref count, free the additional "real" ref // on its behalf. THIS WILL MOST LIKELY RELEASE THE OBJECT AND REMOVE IT // FROM OUR LIST. i->second.first->Release(); } } void ResourceTracker::DidCreateInstance(PP_Instance instance) { CheckThreadingPreconditions(); // Due to the infrastructure of some tests, the instance is registered // twice in a few cases. It would be nice not to do that and assert here // instead. if (instance_map_.find(instance) != instance_map_.end()) return; instance_map_[instance] = make_scoped_ptr(new InstanceData); } void ResourceTracker::DidDeleteInstance(PP_Instance instance) { CheckThreadingPreconditions(); InstanceMap::iterator found_instance = instance_map_.find(instance); // Due to the infrastructure of some tests, the instance is unregistered // twice in a few cases. It would be nice not to do that and assert here // instead. if (found_instance == instance_map_.end()) return; InstanceData& data = *found_instance->second; // Force release all plugin references to resources associated with the // deleted instance. Make a copy since as we iterate through them, each one // will remove itself from the tracking info individually. ResourceSet to_delete = data.resources; ResourceSet::iterator cur = to_delete.begin(); while (cur != to_delete.end()) { // Note that it's remotely possible for the object to already be deleted // from the live resources. One case is if a resource object is holding // the last ref to another. When we release the first one, it will release // the second one. So the second one will be gone when we eventually get // to it. ResourceMap::iterator found_resource = live_resources_.find(*cur); if (found_resource != live_resources_.end()) { Resource* resource = found_resource->second.first; if (found_resource->second.second > 0) { LastPluginRefWasDeleted(resource); found_resource->second.second = 0; // This will most likely delete the resource object and remove it // from the live_resources_ list. resource->Release(); } } cur++; } // In general the above pass will delete all the resources and there won't // be any left in the map. However, if parts of the implementation are still // holding on to internal refs, we need to tell them that the instance is // gone. to_delete = data.resources; cur = to_delete.begin(); while (cur != to_delete.end()) { ResourceMap::iterator found_resource = live_resources_.find(*cur); if (found_resource != live_resources_.end()) found_resource->second.first->NotifyInstanceWasDeleted(); cur++; } instance_map_.erase(instance); } int ResourceTracker::GetLiveObjectsForInstance(PP_Instance instance) const { CheckThreadingPreconditions(); InstanceMap::const_iterator found = instance_map_.find(instance); if (found == instance_map_.end()) return 0; return static_cast(found->second->resources.size()); } void ResourceTracker::UseOddResourceValueInDebugMode() { #if !defined(NDEBUG) DCHECK_EQ(0, last_resource_value_); ++last_resource_value_; #endif } PP_Resource ResourceTracker::AddResource(Resource* object) { CheckThreadingPreconditions(); // If the plugin manages to create too many resources, don't do crazy stuff. if (last_resource_value_ >= kMaxPPId) return 0; // Allocate an ID. Note there's a rare error condition below that means we // could end up not using |new_id|, but that's harmless. PP_Resource new_id = MakeTypedId(GetNextResourceValue(), PP_ID_TYPE_RESOURCE); // Some objects have a 0 instance, meaning they aren't associated with any // instance, so they won't be in |instance_map_|. This is (as of this writing) // only true of the PPB_MessageLoop resource for the main thread. if (object->pp_instance()) { InstanceMap::iterator found = instance_map_.find(object->pp_instance()); if (found == instance_map_.end()) { // If you hit this, it's likely somebody forgot to call DidCreateInstance, // the resource was created with an invalid PP_Instance, or the renderer // side tried to create a resource for a plugin that crashed/exited. This // could happen for OOP plugins where due to reentrancies in context of // outgoing sync calls the renderer can send events after a plugin has // exited. VLOG(1) << "Failed to find plugin instance in instance map"; return 0; } found->second->resources.insert(new_id); } live_resources_[new_id] = ResourceAndRefCount(object, 0); return new_id; } void ResourceTracker::RemoveResource(Resource* object) { CheckThreadingPreconditions(); PP_Resource pp_resource = object->pp_resource(); InstanceMap::iterator found = instance_map_.find(object->pp_instance()); if (found != instance_map_.end()) found->second->resources.erase(pp_resource); live_resources_.erase(pp_resource); } void ResourceTracker::LastPluginRefWasDeleted(Resource* object) { // Bug http://crbug.com/134611 indicates that sometimes the resource tracker // is null here. This should never be the case since if we have a resource in // the tracker, it should always have a valid instance associated with it // (except for the resource for the main thread's message loop, which has // instance set to 0). // As a result, we do some CHECKs here to see what types of problems the // instance might have before dispatching. // // TODO(brettw) remove these checks when this bug is no longer relevant. // Note, we do an imperfect check here; this might be a loop that's not the // main one. const bool is_message_loop = (object->AsPPB_MessageLoop_API() != NULL); CHECK(object->pp_instance() || is_message_loop); CallbackTracker* callback_tracker = PpapiGlobals::Get()->GetCallbackTrackerForInstance(object->pp_instance()); CHECK(callback_tracker || is_message_loop); if (callback_tracker) callback_tracker->PostAbortForResource(object->pp_resource()); object->NotifyLastPluginRefWasDeleted(); } int32_t ResourceTracker::GetNextResourceValue() { #if defined(NDEBUG) return ++last_resource_value_; #else // In debug mode, the least significant bit indicates which side (renderer // or plugin process) created the resource. Increment by 2 so it's always the // same. last_resource_value_ += 2; return last_resource_value_; #endif } bool ResourceTracker::CanOperateOnResource(PP_Resource res) { #if defined(NDEBUG) return true; #else // The invalid PP_Resource value could appear at both sides. if (res == 0) return true; // Skipping the type bits, the least significant bit of |res| should be the // same as that of |last_resource_value_|. return ((res >> kPPIdTypeBits) & 1) == (last_resource_value_ & 1); #endif } } // namespace ppapi