blob: 7a1d81bf050ac2bb95506ee67182791cafb31636 [file] [log] [blame]
levin@chromium.org5c528682011-03-28 10:54:15 +09001// Copyright (c) 2011 The Chromium Authors. All rights reserved.
mbelshe@google.com877d2032008-10-23 08:09:21 +09002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef BASE_OBSERVER_LIST_THREADSAFE_H_
6#define BASE_OBSERVER_LIST_THREADSAFE_H_
thakis@chromium.org01d14522010-07-27 08:08:24 +09007#pragma once
mbelshe@google.com877d2032008-10-23 08:09:21 +09008
erg@chromium.org6f206e72010-07-15 03:58:17 +09009#include <algorithm>
erg@chromium.orga7528522010-07-16 02:23:23 +090010#include <map>
mbelshe@google.com877d2032008-10-23 08:09:21 +090011
12#include "base/basictypes.h"
jhawkins@chromium.org22d67562011-10-13 05:26:50 +090013#include "base/bind.h"
jhawkins@chromium.orgf7b5ad92011-05-10 09:37:40 +090014#include "base/callback_old.h"
ajwong@chromium.org8e2e3002011-09-22 03:05:41 +090015#include "base/location.h"
mbelshe@google.com877d2032008-10-23 08:09:21 +090016#include "base/logging.h"
levin@chromium.org5c528682011-03-28 10:54:15 +090017#include "base/memory/ref_counted.h"
mbelshe@google.com877d2032008-10-23 08:09:21 +090018#include "base/message_loop.h"
adamk@chromium.org2bc73e72011-08-10 06:29:59 +090019#include "base/message_loop_proxy.h"
mbelshe@google.com877d2032008-10-23 08:09:21 +090020#include "base/observer_list.h"
mbelshe@google.com877d2032008-10-23 08:09:21 +090021#include "base/task.h"
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +090022#include "base/threading/platform_thread.h"
mbelshe@google.com877d2032008-10-23 08:09:21 +090023
24///////////////////////////////////////////////////////////////////////////////
25//
26// OVERVIEW:
27//
28// A thread-safe container for a list of observers.
maruel@chromium.org8fe7adc2009-03-04 00:01:12 +090029// This is similar to the observer_list (see observer_list.h), but it
mbelshe@google.com877d2032008-10-23 08:09:21 +090030// is more robust for multi-threaded situations.
maruel@chromium.org8fe7adc2009-03-04 00:01:12 +090031//
mbelshe@google.com877d2032008-10-23 08:09:21 +090032// The following use cases are supported:
33// * Observers can register for notifications from any thread.
34// Callbacks to the observer will occur on the same thread where
35// the observer initially called AddObserver() from.
pkasting@chromium.orgaed4e582010-06-26 06:30:38 +090036// * Any thread may trigger a notification via Notify().
mbelshe@google.com877d2032008-10-23 08:09:21 +090037// * Observers can remove themselves from the observer list inside
38// of a callback.
39// * If one thread is notifying observers concurrently with an observer
40// removing itself from the observer list, the notifications will
41// be silently dropped.
42//
43// The drawback of the threadsafe observer list is that notifications
44// are not as real-time as the non-threadsafe version of this class.
45// Notifications will always be done via PostTask() to another thread,
46// whereas with the non-thread-safe observer_list, notifications happen
47// synchronously and immediately.
48//
49// IMPLEMENTATION NOTES
50// The ObserverListThreadSafe maintains an ObserverList for each thread
maruel@chromium.org8fe7adc2009-03-04 00:01:12 +090051// which uses the ThreadSafeObserver. When Notifying the observers,
mbelshe@google.com877d2032008-10-23 08:09:21 +090052// we simply call PostTask to each registered thread, and then each thread
53// will notify its regular ObserverList.
54//
55///////////////////////////////////////////////////////////////////////////////
akalin@chromium.orge43be3d2011-01-12 06:19:54 +090056
57// Forward declaration for ObserverListThreadSafeTraits.
58template <class ObserverType>
59class ObserverListThreadSafe;
60
61// This class is used to work around VS2005 not accepting:
62//
63// friend class
64// base::RefCountedThreadSafe<ObserverListThreadSafe<ObserverType> >;
65//
66// Instead of friending the class, we could friend the actual function
67// which calls delete. However, this ends up being
68// RefCountedThreadSafe::DeleteInternal(), which is private. So we
69// define our own templated traits class so we can friend it.
70template <class T>
71struct ObserverListThreadSafeTraits {
72 static void Destruct(const ObserverListThreadSafe<T>* x) {
73 delete x;
74 }
75};
76
mbelshe@google.com877d2032008-10-23 08:09:21 +090077template <class ObserverType>
maruel@chromium.org8fe7adc2009-03-04 00:01:12 +090078class ObserverListThreadSafe
akalin@chromium.orge43be3d2011-01-12 06:19:54 +090079 : public base::RefCountedThreadSafe<
80 ObserverListThreadSafe<ObserverType>,
81 ObserverListThreadSafeTraits<ObserverType> > {
mbelshe@google.com877d2032008-10-23 08:09:21 +090082 public:
willchan@chromium.org4c7f03e2010-09-01 07:54:21 +090083 typedef typename ObserverList<ObserverType>::NotificationType
84 NotificationType;
85
86 ObserverListThreadSafe()
87 : type_(ObserverListBase<ObserverType>::NOTIFY_ALL) {}
88 explicit ObserverListThreadSafe(NotificationType type) : type_(type) {}
mbelshe@google.com877d2032008-10-23 08:09:21 +090089
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +090090 // Add an observer to the list. An observer should not be added to
91 // the same list more than once.
mbelshe@google.com877d2032008-10-23 08:09:21 +090092 void AddObserver(ObserverType* obs) {
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +090093 // If there is not a current MessageLoop, it is impossible to notify on it,
94 // so do not add the observer.
95 if (!MessageLoop::current())
96 return;
97
mbelshe@google.com877d2032008-10-23 08:09:21 +090098 ObserverList<ObserverType>* list = NULL;
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +090099 base::PlatformThreadId thread_id = base::PlatformThread::CurrentId();
mbelshe@google.com877d2032008-10-23 08:09:21 +0900100 {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900101 base::AutoLock lock(list_lock_);
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900102 if (observer_lists_.find(thread_id) == observer_lists_.end())
103 observer_lists_[thread_id] = new ObserverListContext(type_);
104 list = &(observer_lists_[thread_id]->list);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900105 }
106 list->AddObserver(obs);
107 }
108
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +0900109 // Remove an observer from the list if it is in the list.
mbelshe@google.com877d2032008-10-23 08:09:21 +0900110 // If there are pending notifications in-transit to the observer, they will
111 // be aborted.
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +0900112 // If the observer to be removed is in the list, RemoveObserver MUST
113 // be called from the same thread which called AddObserver.
mbelshe@google.com877d2032008-10-23 08:09:21 +0900114 void RemoveObserver(ObserverType* obs) {
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900115 ObserverListContext* context = NULL;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900116 ObserverList<ObserverType>* list = NULL;
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900117 base::PlatformThreadId thread_id = base::PlatformThread::CurrentId();
mbelshe@google.com877d2032008-10-23 08:09:21 +0900118 {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900119 base::AutoLock lock(list_lock_);
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900120 typename ObserversListMap::iterator it = observer_lists_.find(thread_id);
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +0900121 if (it == observer_lists_.end()) {
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900122 // This will happen if we try to remove an observer on a thread
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +0900123 // we never added an observer for.
mbelshe@google.com2d95cab2008-11-04 02:15:07 +0900124 return;
125 }
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900126 context = it->second;
127 list = &context->list;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900128
129 // If we're about to remove the last observer from the list,
130 // then we can remove this observer_list entirely.
akalin@chromium.orgcbec5fb2011-06-05 16:07:12 +0900131 if (list->HasObserver(obs) && list->size() == 1)
132 observer_lists_.erase(it);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900133 }
134 list->RemoveObserver(obs);
135
136 // If RemoveObserver is called from a notification, the size will be
137 // nonzero. Instead of deleting here, the NotifyWrapper will delete
138 // when it finishes iterating.
139 if (list->size() == 0)
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900140 delete context;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900141 }
142
143 // Notify methods.
144 // Make a thread-safe callback to each Observer in the list.
145 // Note, these calls are effectively asynchronous. You cannot assume
146 // that at the completion of the Notify call that all Observers have
147 // been Notified. The notification may still be pending delivery.
148 template <class Method>
149 void Notify(Method m) {
150 UnboundMethod<ObserverType, Method, Tuple0> method(m, MakeTuple());
151 Notify<Method, Tuple0>(method);
152 }
153
154 template <class Method, class A>
akalin@chromium.org59a0fef2011-06-17 07:32:14 +0900155 void Notify(Method m, const A& a) {
mbelshe@google.com877d2032008-10-23 08:09:21 +0900156 UnboundMethod<ObserverType, Method, Tuple1<A> > method(m, MakeTuple(a));
157 Notify<Method, Tuple1<A> >(method);
158 }
159
akalin@chromium.org59a0fef2011-06-17 07:32:14 +0900160 template <class Method, class A, class B>
161 void Notify(Method m, const A& a, const B& b) {
162 UnboundMethod<ObserverType, Method, Tuple2<A, B> > method(
163 m, MakeTuple(a, b));
164 Notify<Method, Tuple2<A, B> >(method);
165 }
166
167 template <class Method, class A, class B, class C>
168 void Notify(Method m, const A& a, const B& b, const C& c) {
169 UnboundMethod<ObserverType, Method, Tuple3<A, B, C> > method(
170 m, MakeTuple(a, b, c));
171 Notify<Method, Tuple3<A, B, C> >(method);
172 }
173
174 template <class Method, class A, class B, class C, class D>
175 void Notify(Method m, const A& a, const B& b, const C& c, const D& d) {
176 UnboundMethod<ObserverType, Method, Tuple4<A, B, C, D> > method(
177 m, MakeTuple(a, b, c, d));
178 Notify<Method, Tuple4<A, B, C, D> >(method);
179 }
180
mbelshe@google.com877d2032008-10-23 08:09:21 +0900181 // TODO(mbelshe): Add more wrappers for Notify() with more arguments.
182
183 private:
akalin@chromium.orge43be3d2011-01-12 06:19:54 +0900184 // See comment above ObserverListThreadSafeTraits' definition.
185 friend struct ObserverListThreadSafeTraits<ObserverType>;
186
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900187 struct ObserverListContext {
188 explicit ObserverListContext(NotificationType type)
nduca@chromium.orgba048612011-08-16 05:33:46 +0900189 : loop(base::MessageLoopProxy::current()),
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900190 list(type) {
191 }
192
193 scoped_refptr<base::MessageLoopProxy> loop;
194 ObserverList<ObserverType> list;
195
196 DISALLOW_COPY_AND_ASSIGN(ObserverListContext);
197 };
198
akalin@chromium.org982d9d52011-01-11 05:40:36 +0900199 ~ObserverListThreadSafe() {
200 typename ObserversListMap::const_iterator it;
201 for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it)
202 delete (*it).second;
203 observer_lists_.clear();
204 }
205
mbelshe@google.com877d2032008-10-23 08:09:21 +0900206 template <class Method, class Params>
207 void Notify(const UnboundMethod<ObserverType, Method, Params>& method) {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900208 base::AutoLock lock(list_lock_);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900209 typename ObserversListMap::iterator it;
210 for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it) {
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900211 ObserverListContext* context = (*it).second;
212 context->loop->PostTask(
willchan@chromium.orgd52d47f2010-02-13 06:38:37 +0900213 FROM_HERE,
jhawkins@chromium.org22d67562011-10-13 05:26:50 +0900214 base::Bind(&ObserverListThreadSafe<ObserverType>::
215 template NotifyWrapper<Method, Params>, this, context, method));
mbelshe@google.com877d2032008-10-23 08:09:21 +0900216 }
217 }
218
219 // Wrapper which is called to fire the notifications for each thread's
220 // ObserverList. This function MUST be called on the thread which owns
221 // the unsafe ObserverList.
222 template <class Method, class Params>
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900223 void NotifyWrapper(ObserverListContext* context,
mbelshe@google.com877d2032008-10-23 08:09:21 +0900224 const UnboundMethod<ObserverType, Method, Params>& method) {
225
226 // Check that this list still needs notifications.
227 {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900228 base::AutoLock lock(list_lock_);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900229 typename ObserversListMap::iterator it =
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900230 observer_lists_.find(base::PlatformThread::CurrentId());
mbelshe@google.com877d2032008-10-23 08:09:21 +0900231
232 // The ObserverList could have been removed already. In fact, it could
233 // have been removed and then re-added! If the master list's loop
234 // does not match this one, then we do not need to finish this
235 // notification.
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900236 if (it == observer_lists_.end() || it->second != context)
mbelshe@google.com877d2032008-10-23 08:09:21 +0900237 return;
238 }
239
240 {
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900241 typename ObserverList<ObserverType>::Iterator it(context->list);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900242 ObserverType* obs;
243 while ((obs = it.GetNext()) != NULL)
244 method.Run(obs);
245 }
246
247 // If there are no more observers on the list, we can now delete it.
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900248 if (context->list.size() == 0) {
mbelshe@google.com877d2032008-10-23 08:09:21 +0900249 {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900250 base::AutoLock lock(list_lock_);
willchan@chromium.org2777ca52010-09-18 04:33:06 +0900251 // Remove |list| if it's not already removed.
252 // This can happen if multiple observers got removed in a notification.
253 // See http://crbug.com/55725.
mbelshe@google.com877d2032008-10-23 08:09:21 +0900254 typename ObserversListMap::iterator it =
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900255 observer_lists_.find(base::PlatformThread::CurrentId());
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900256 if (it != observer_lists_.end() && it->second == context)
willchan@chromium.org2777ca52010-09-18 04:33:06 +0900257 observer_lists_.erase(it);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900258 }
adamk@chromium.org2bc73e72011-08-10 06:29:59 +0900259 delete context;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900260 }
261 }
262
rsesek@chromium.org1aab77c2011-11-24 05:34:04 +0900263 // Key by PlatformThreadId because in tests, clients can attempt to remove
264 // observers without a MessageLoop. If this were keyed by MessageLoop, that
265 // operation would be silently ignored, leaving garbage in the ObserverList.
266 typedef std::map<base::PlatformThreadId, ObserverListContext*>
267 ObserversListMap;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900268
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900269 base::Lock list_lock_; // Protects the observer_lists_.
mbelshe@google.com877d2032008-10-23 08:09:21 +0900270 ObserversListMap observer_lists_;
willchan@chromium.org4c7f03e2010-09-01 07:54:21 +0900271 const NotificationType type_;
mbelshe@google.com877d2032008-10-23 08:09:21 +0900272
tfarina@chromium.org0aad8422010-06-05 01:13:51 +0900273 DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe);
mbelshe@google.com877d2032008-10-23 08:09:21 +0900274};
275
276#endif // BASE_OBSERVER_LIST_THREADSAFE_H_