summaryrefslogtreecommitdiffstats
path: root/content/browser/renderer_data_memoizing_store.h
blob: 035f70b7e3c4838113ff9d8027797fb2500c4b00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright 2013 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.

#ifndef CONTENT_BROWSER_RENDERER_DATA_MEMOIZING_STORE_H_
#define CONTENT_BROWSER_RENDERER_DATA_MEMOIZING_STORE_H_

#include <map>

#include "base/bind.h"
#include "base/synchronization/lock.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"

namespace content {

// RendererDataMemoizingStore is a thread-safe container that retains reference
// counted objects that are associated with one or more render processes.
// Objects are identified by an int and only a single reference to a given
// object is retained. RendererDataMemoizingStore watches for render process
// termination and releases objects that are no longer associated with any
// render process.
//
// TODO(jcampan): Rather than watching for render process termination, we should
//                instead be listening to events such as resource cached/
//                removed from cache, and remove the items when we know they
//                are not used anymore.
template <typename T>
class RendererDataMemoizingStore : public RenderProcessHostObserver {
 public:
  RendererDataMemoizingStore() : next_item_id_(1) {
  }

  ~RendererDataMemoizingStore() {
    DCHECK_EQ(0U, id_to_item_.size()) << "Failed to outlive render processes";
  }

  // Store adds |item| to this collection, associates it with the given render
  // process id and returns an opaque identifier for it. If |item| is already
  // known, the same identifier will be returned.
  int Store(T* item, int process_id) {
    DCHECK(item);
    base::AutoLock auto_lock(lock_);

    int item_id;

    // Do we already know this item?
    typename ReverseItemMap::iterator item_iter = item_to_id_.find(item);
    if (item_iter == item_to_id_.end()) {
      item_id = next_item_id_++;
      // We use 0 as an invalid item_id value.  In the unlikely event that
      // next_item_id_ wraps around, we reset it to 1.
      if (next_item_id_ == 0)
        next_item_id_ = 1;
      id_to_item_[item_id] = item;
      item_to_id_[item] = item_id;
    } else {
      item_id = item_iter->second;
    }

    // Let's update process_id_to_item_id_.
    std::pair<IDMap::iterator, IDMap::iterator> process_ids =
        process_id_to_item_id_.equal_range(process_id);
    bool already_watching_process = (process_ids.first != process_ids.second);
    if (std::find_if(process_ids.first, process_ids.second,
                     MatchSecond<int>(item_id)) == process_ids.second) {
      process_id_to_item_id_.insert(std::make_pair(process_id, item_id));
    }

    // And item_id_to_process_id_.
    std::pair<IDMap::iterator, IDMap::iterator> item_ids =
        item_id_to_process_id_.equal_range(item_id);
    if (std::find_if(item_ids.first, item_ids.second,
                     MatchSecond<int>(process_id)) == item_ids.second) {
      item_id_to_process_id_.insert(std::make_pair(item_id, process_id));
    }

    // If we're not doing so already, keep an eye for the process host deletion.
    if (!already_watching_process) {
      if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        StartObservingProcess(process_id);
      } else {
        BrowserThread::PostTask(
            BrowserThread::UI,
            FROM_HERE,
            base::Bind(&RendererDataMemoizingStore::StartObservingProcess,
                       base::Unretained(this),
                       process_id));
      }
    }

    DCHECK(item_id);
    return item_id;
  }

  // Retrieve fetches a previously Stored() item, identified by |item_id|.
  // If |item_id| is recognized, |item| will be updated and Retrieve() will
  // return true, it will otherwise return false.
  bool Retrieve(int item_id, scoped_refptr<T>* item) {
    base::AutoLock auto_lock(lock_);

    typename ItemMap::iterator iter = id_to_item_.find(item_id);
    if (iter == id_to_item_.end())
      return false;
    if (item)
      *item = iter->second;
    return true;
  }

 private:
  typedef std::multimap<int, int> IDMap;
  typedef std::map<int, scoped_refptr<T> > ItemMap;
  typedef std::map<T*, int, typename T::LessThan> ReverseItemMap;

  template <typename M>
  struct MatchSecond {
    explicit MatchSecond(const M& t) : value(t) {}

    template <typename Pair>
    bool operator()(const Pair& p) const {
      return (value == p.second);
    }

    M value;
  };

  void StartObservingProcess(int process_id) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    RenderProcessHost* host = RenderProcessHost::FromID(process_id);
    if (!host) {
      // We lost the race to observe the host before it was destroyed. Since
      // this function was called because we're managing objects tied to that
      // (now destroyed) RenderProcessHost, let's clean up.
      RemoveRenderProcessItems(process_id);
      return;
    }

    host->AddObserver(this);
  }

  // Remove the item specified by |item_id| from id_to_item_ and item_to_id_.
  // NOTE: the caller (RemoveRenderProcessItems) must hold lock_.
  void RemoveInternal(int item_id) {
    typename ItemMap::iterator item_iter = id_to_item_.find(item_id);
    DCHECK(item_iter != id_to_item_.end());

    typename ReverseItemMap::iterator id_iter =
        item_to_id_.find(item_iter->second.get());
    DCHECK(id_iter != item_to_id_.end());
    item_to_id_.erase(id_iter);

    id_to_item_.erase(item_iter);
  }

  void RenderProcessHostDestroyed(RenderProcessHost* host) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    RemoveRenderProcessItems(host->GetID());
  }

  // Removes all the items associated with the specified process from the store.
  void RemoveRenderProcessItems(int process_id) {
    base::AutoLock auto_lock(lock_);

    // We iterate through all the item ids for that process.
    std::pair<IDMap::iterator, IDMap::iterator> process_ids =
        process_id_to_item_id_.equal_range(process_id);
    for (IDMap::iterator ids_iter = process_ids.first;
         ids_iter != process_ids.second; ++ids_iter) {
      int item_id = ids_iter->second;
      // Find all the processes referring to this item id in
      // item_id_to_process_id_, then locate the process being removed within
      // that range.
      std::pair<IDMap::iterator, IDMap::iterator> item_ids =
          item_id_to_process_id_.equal_range(item_id);
      IDMap::iterator proc_iter = std::find_if(
          item_ids.first, item_ids.second, MatchSecond<int>(process_id));
      DCHECK(proc_iter != item_ids.second);

      // Before removing, determine if no other processes refer to the current
      // item id. If |proc_iter| (the current process) is the lower bound of
      // processes containing the current item id and if |next_proc_iter| is the
      // upper bound (the first process that does not), then only one process,
      // the one being removed, refers to the item id.
      IDMap::iterator next_proc_iter = proc_iter;
      ++next_proc_iter;
      bool last_process_for_item_id =
          (proc_iter == item_ids.first && next_proc_iter == item_ids.second);
      item_id_to_process_id_.erase(proc_iter);

      if (last_process_for_item_id) {
        // The current item id is not referenced by any other processes, so
        // remove it from id_to_item_ and item_to_id_.
        RemoveInternal(item_id);
      }
    }
    if (process_ids.first != process_ids.second)
      process_id_to_item_id_.erase(process_ids.first, process_ids.second);
  }

  IDMap process_id_to_item_id_;
  IDMap item_id_to_process_id_;
  ItemMap id_to_item_;
  ReverseItemMap item_to_id_;

  int next_item_id_;

  // This lock protects: process_id_to_item_id_, item_id_to_process_id_,
  //                     id_to_item_, and item_to_id_.
  base::Lock lock_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_DATA_MEMOIZING_STORE_H_