api_resource_manager.h revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2014 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#ifndef EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_ 6#define EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_ 7 8#include <map> 9 10#include "base/containers/hash_tables.h" 11#include "base/memory/linked_ptr.h" 12#include "base/memory/ref_counted.h" 13#include "base/scoped_observer.h" 14#include "base/threading/non_thread_safe.h" 15#include "components/keyed_service/core/keyed_service.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/browser/notification_observer.h" 18#include "content/public/browser/notification_registrar.h" 19#include "content/public/browser/notification_service.h" 20#include "extensions/browser/browser_context_keyed_api_factory.h" 21#include "extensions/browser/extension_host.h" 22#include "extensions/browser/extension_registry.h" 23#include "extensions/browser/extension_registry_observer.h" 24#include "extensions/browser/notification_types.h" 25#include "extensions/common/extension.h" 26 27namespace extensions { 28 29namespace core_api { 30class BluetoothSocketApiFunction; 31class BluetoothSocketEventDispatcher; 32class SerialEventDispatcher; 33class TCPServerSocketEventDispatcher; 34class TCPSocketEventDispatcher; 35class UDPSocketEventDispatcher; 36} 37 38template <typename T> 39struct NamedThreadTraits { 40 static bool IsCalledOnValidThread() { 41 return content::BrowserThread::CurrentlyOn(T::kThreadId); 42 } 43 44 static bool IsMessageLoopValid() { 45 return content::BrowserThread::IsMessageLoopValid(T::kThreadId); 46 } 47 48 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 49 return content::BrowserThread::GetMessageLoopProxyForThread(T::kThreadId); 50 } 51}; 52 53template <typename T> 54struct TestThreadTraits { 55 static bool IsCalledOnValidThread() { 56 return content::BrowserThread::CurrentlyOn(thread_id_); 57 } 58 59 static bool IsMessageLoopValid() { 60 return content::BrowserThread::IsMessageLoopValid(thread_id_); 61 } 62 63 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 64 return content::BrowserThread::GetMessageLoopProxyForThread(thread_id_); 65 } 66 67 static content::BrowserThread::ID thread_id_; 68}; 69 70template <typename T> 71content::BrowserThread::ID TestThreadTraits<T>::thread_id_ = 72 content::BrowserThread::IO; 73 74// An ApiResourceManager manages the lifetime of a set of resources that 75// that live on named threads (i.e. BrowserThread::IO) which ApiFunctions use. 76// Examples of such resources are sockets or USB connections. 77// 78// Users of this class should define kThreadId to be the thread that 79// ApiResourceManager to works on. The default is defined in ApiResource. 80// The user must also define a static const char* service_name() that returns 81// the name of the service, and in order for ApiResourceManager to use 82// service_name() friend this class. 83// 84// In the cc file the user must define a GetFactoryInstance() and manage their 85// own instances (typically using LazyInstance or Singleton). 86// 87// E.g.: 88// 89// class Resource { 90// public: 91// static const BrowserThread::ID kThreadId = BrowserThread::FILE; 92// private: 93// friend class ApiResourceManager<Resource>; 94// static const char* service_name() { 95// return "ResourceManager"; 96// } 97// }; 98// 99// In the cc file: 100// 101// static base::LazyInstance<BrowserContextKeyedAPIFactory< 102// ApiResourceManager<Resource> > > 103// g_factory = LAZY_INSTANCE_INITIALIZER; 104// 105// 106// template <> 107// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >* 108// ApiResourceManager<Resource>::GetFactoryInstance() { 109// return g_factory.Pointer(); 110// } 111template <class T, typename ThreadingTraits = NamedThreadTraits<T> > 112class ApiResourceManager : public BrowserContextKeyedAPI, 113 public base::NonThreadSafe, 114 public content::NotificationObserver, 115 public ExtensionRegistryObserver { 116 public: 117 explicit ApiResourceManager(content::BrowserContext* context) 118 : data_(new ApiResourceData()), extension_registry_observer_(this) { 119 extension_registry_observer_.Add(ExtensionRegistry::Get(context)); 120 registrar_.Add(this, 121 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, 122 content::NotificationService::AllSources()); 123 } 124 // For Testing. 125 static ApiResourceManager<T, TestThreadTraits<T> >* 126 CreateApiResourceManagerForTest(content::BrowserContext* context, 127 content::BrowserThread::ID thread_id) { 128 TestThreadTraits<T>::thread_id_ = thread_id; 129 ApiResourceManager<T, TestThreadTraits<T> >* manager = 130 new ApiResourceManager<T, TestThreadTraits<T> >(context); 131 return manager; 132 } 133 134 virtual ~ApiResourceManager() { 135 DCHECK(CalledOnValidThread()); 136 DCHECK(ThreadingTraits::IsMessageLoopValid()) 137 << "A unit test is using an ApiResourceManager but didn't provide " 138 "the thread message loop needed for that kind of resource. " 139 "Please ensure that the appropriate message loop is operational."; 140 141 data_->InititateCleanup(); 142 } 143 144 // Takes ownership. 145 int Add(T* api_resource) { return data_->Add(api_resource); } 146 147 void Remove(const std::string& extension_id, int api_resource_id) { 148 data_->Remove(extension_id, api_resource_id); 149 } 150 151 T* Get(const std::string& extension_id, int api_resource_id) { 152 return data_->Get(extension_id, api_resource_id); 153 } 154 155 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 156 return data_->GetResourceIds(extension_id); 157 } 158 159 // BrowserContextKeyedAPI implementation. 160 static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >* 161 GetFactoryInstance(); 162 163 // Convenience method to get the ApiResourceManager for a profile. 164 static ApiResourceManager<T>* Get(content::BrowserContext* context) { 165 return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context); 166 } 167 168 // BrowserContextKeyedAPI implementation. 169 static const char* service_name() { return T::service_name(); } 170 171 // Change the resource mapped to this |extension_id| at this 172 // |api_resource_id| to |resource|. Returns true and succeeds unless 173 // |api_resource_id| does not already identify a resource held by 174 // |extension_id|. 175 bool Replace(const std::string& extension_id, 176 int api_resource_id, 177 T* resource) { 178 return data_->Replace(extension_id, api_resource_id, resource); 179 } 180 181 protected: 182 // content::NotificationObserver: 183 virtual void Observe(int type, 184 const content::NotificationSource& source, 185 const content::NotificationDetails& details) OVERRIDE { 186 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type); 187 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 188 data_->InitiateExtensionSuspendedCleanup(host->extension_id()); 189 } 190 191 // ExtensionRegistryObserver: 192 virtual void OnExtensionUnloaded( 193 content::BrowserContext* browser_context, 194 const Extension* extension, 195 UnloadedExtensionInfo::Reason reason) OVERRIDE { 196 data_->InitiateExtensionUnloadedCleanup(extension->id()); 197 } 198 199 private: 200 // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and 201 // we could avoid maintaining a friends list here. 202 friend class BluetoothAPI; 203 friend class core_api::BluetoothSocketApiFunction; 204 friend class core_api::BluetoothSocketEventDispatcher; 205 friend class core_api::SerialEventDispatcher; 206 friend class core_api::TCPServerSocketEventDispatcher; 207 friend class core_api::TCPSocketEventDispatcher; 208 friend class core_api::UDPSocketEventDispatcher; 209 friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >; 210 211 static const bool kServiceHasOwnInstanceInIncognito = true; 212 static const bool kServiceIsNULLWhileTesting = true; 213 214 // ApiResourceData class handles resource bookkeeping on a thread 215 // where resource lifetime is handled. 216 class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> { 217 public: 218 typedef std::map<int, linked_ptr<T> > ApiResourceMap; 219 // Lookup map from extension id's to allocated resource id's. 220 typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap; 221 222 ApiResourceData() : next_id_(1) {} 223 224 int Add(T* api_resource) { 225 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 226 int id = GenerateId(); 227 if (id > 0) { 228 linked_ptr<T> resource_ptr(api_resource); 229 api_resource_map_[id] = resource_ptr; 230 231 const std::string& extension_id = api_resource->owner_extension_id(); 232 ExtensionToResourceMap::iterator it = 233 extension_resource_map_.find(extension_id); 234 if (it == extension_resource_map_.end()) { 235 it = extension_resource_map_.insert( 236 std::make_pair(extension_id, base::hash_set<int>())).first; 237 } 238 it->second.insert(id); 239 return id; 240 } 241 return 0; 242 } 243 244 void Remove(const std::string& extension_id, int api_resource_id) { 245 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 246 if (GetOwnedResource(extension_id, api_resource_id)) { 247 ExtensionToResourceMap::iterator it = 248 extension_resource_map_.find(extension_id); 249 it->second.erase(api_resource_id); 250 api_resource_map_.erase(api_resource_id); 251 } 252 } 253 254 T* Get(const std::string& extension_id, int api_resource_id) { 255 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 256 return GetOwnedResource(extension_id, api_resource_id); 257 } 258 259 // Change the resource mapped to this |extension_id| at this 260 // |api_resource_id| to |resource|. Returns true and succeeds unless 261 // |api_resource_id| does not already identify a resource held by 262 // |extension_id|. 263 bool Replace(const std::string& extension_id, 264 int api_resource_id, 265 T* api_resource) { 266 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 267 T* old_resource = api_resource_map_[api_resource_id].get(); 268 if (old_resource && extension_id == old_resource->owner_extension_id()) { 269 api_resource_map_[api_resource_id] = linked_ptr<T>(api_resource); 270 return true; 271 } 272 return false; 273 } 274 275 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 276 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 277 return GetOwnedResourceIds(extension_id); 278 } 279 280 void InitiateExtensionUnloadedCleanup(const std::string& extension_id) { 281 if (ThreadingTraits::IsCalledOnValidThread()) { 282 CleanupResourcesFromUnloadedExtension(extension_id); 283 } else { 284 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 285 FROM_HERE, 286 base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension, 287 this, 288 extension_id)); 289 } 290 } 291 292 void InitiateExtensionSuspendedCleanup(const std::string& extension_id) { 293 if (ThreadingTraits::IsCalledOnValidThread()) { 294 CleanupResourcesFromSuspendedExtension(extension_id); 295 } else { 296 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 297 FROM_HERE, 298 base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension, 299 this, 300 extension_id)); 301 } 302 } 303 304 void InititateCleanup() { 305 if (ThreadingTraits::IsCalledOnValidThread()) { 306 Cleanup(); 307 } else { 308 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 309 FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this)); 310 } 311 } 312 313 private: 314 friend class base::RefCountedThreadSafe<ApiResourceData>; 315 316 virtual ~ApiResourceData() {} 317 318 T* GetOwnedResource(const std::string& extension_id, int api_resource_id) { 319 linked_ptr<T> ptr = api_resource_map_[api_resource_id]; 320 T* resource = ptr.get(); 321 if (resource && extension_id == resource->owner_extension_id()) 322 return resource; 323 return NULL; 324 } 325 326 base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) { 327 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 328 ExtensionToResourceMap::iterator it = 329 extension_resource_map_.find(extension_id); 330 if (it == extension_resource_map_.end()) 331 return NULL; 332 return &(it->second); 333 } 334 335 void CleanupResourcesFromUnloadedExtension( 336 const std::string& extension_id) { 337 CleanupResourcesFromExtension(extension_id, true); 338 } 339 340 void CleanupResourcesFromSuspendedExtension( 341 const std::string& extension_id) { 342 CleanupResourcesFromExtension(extension_id, false); 343 } 344 345 void CleanupResourcesFromExtension(const std::string& extension_id, 346 bool remove_all) { 347 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 348 349 ExtensionToResourceMap::iterator it = 350 extension_resource_map_.find(extension_id); 351 if (it == extension_resource_map_.end()) 352 return; 353 354 // Remove all resources, or the non persistent ones only if |remove_all| 355 // is false. 356 base::hash_set<int>& resource_ids = it->second; 357 for (base::hash_set<int>::iterator it = resource_ids.begin(); 358 it != resource_ids.end();) { 359 bool erase = false; 360 if (remove_all) { 361 erase = true; 362 } else { 363 linked_ptr<T> ptr = api_resource_map_[*it]; 364 T* resource = ptr.get(); 365 erase = (resource && !resource->IsPersistent()); 366 } 367 368 if (erase) { 369 api_resource_map_.erase(*it); 370 resource_ids.erase(it++); 371 } else { 372 ++it; 373 } 374 } // end for 375 376 // Remove extension entry if we removed all its resources. 377 if (resource_ids.size() == 0) { 378 extension_resource_map_.erase(extension_id); 379 } 380 } 381 382 void Cleanup() { 383 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 384 385 api_resource_map_.clear(); 386 extension_resource_map_.clear(); 387 } 388 389 int GenerateId() { return next_id_++; } 390 391 int next_id_; 392 ApiResourceMap api_resource_map_; 393 ExtensionToResourceMap extension_resource_map_; 394 }; 395 396 content::NotificationRegistrar registrar_; 397 scoped_refptr<ApiResourceData> data_; 398 399 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> 400 extension_registry_observer_; 401}; 402 403// With WorkerPoolThreadTraits, ApiResourceManager can be used to manage the 404// lifetime of a set of resources that live on sequenced task runner threads 405// which ApiFunctions use. Examples of such resources are temporary file 406// resources produced by certain API calls. 407// 408// Instead of kThreadId. classes used for tracking such resources should define 409// kSequenceToken and kShutdownBehavior to identify sequence task runner for 410// ApiResourceManager to work on and how pending tasks should behave on 411// shutdown. 412// The user must also define a static const char* service_name() that returns 413// the name of the service, and in order for ApiWorkerPoolResourceManager to use 414// service_name() friend this class. 415// 416// In the cc file the user must define a GetFactoryInstance() and manage their 417// own instances (typically using LazyInstance or Singleton). 418// 419// E.g.: 420// 421// class PoolResource { 422// public: 423// static const char kSequenceToken[] = "temp_files"; 424// static const base::SequencedWorkerPool::WorkerShutdown kShutdownBehavior = 425// base::SequencedWorkerPool::BLOCK_SHUTDOWN; 426// private: 427// friend class ApiResourceManager<WorkerPoolResource, 428// WorkerPoolThreadTraits>; 429// static const char* service_name() { 430// return "TempFilesResourceManager"; 431// } 432// }; 433// 434// In the cc file: 435// 436// static base::LazyInstance<BrowserContextKeyedAPIFactory< 437// ApiResourceManager<Resource, WorkerPoolThreadTraits> > > 438// g_factory = LAZY_INSTANCE_INITIALIZER; 439// 440// 441// template <> 442// BrowserContextKeyedAPIFactory<ApiResourceManager<WorkerPoolResource> >* 443// ApiResourceManager<WorkerPoolPoolResource, 444// WorkerPoolThreadTraits>::GetFactoryInstance() { 445// return g_factory.Pointer(); 446// } 447template <typename T> 448struct WorkerPoolThreadTraits { 449 static bool IsCalledOnValidThread() { 450 return content::BrowserThread::GetBlockingPool() 451 ->IsRunningSequenceOnCurrentThread( 452 content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken( 453 T::kSequenceToken)); 454 } 455 456 static bool IsMessageLoopValid() { 457 return content::BrowserThread::GetBlockingPool() != NULL; 458 } 459 460 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 461 return content::BrowserThread::GetBlockingPool() 462 ->GetSequencedTaskRunnerWithShutdownBehavior( 463 content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken( 464 T::kSequenceToken), 465 T::kShutdownBehavior); 466 } 467}; 468 469} // namespace extensions 470 471#endif // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_ 472