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