levin@chromium.org | 5c52868 | 2011-03-28 10:54:15 +0900 | [diff] [blame] | 1 | // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 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 BASE_OBSERVER_LIST_THREADSAFE_H_ |
| 6 | #define BASE_OBSERVER_LIST_THREADSAFE_H_ |
thakis@chromium.org | 01d1452 | 2010-07-27 08:08:24 +0900 | [diff] [blame] | 7 | #pragma once |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 8 | |
erg@chromium.org | 6f206e7 | 2010-07-15 03:58:17 +0900 | [diff] [blame] | 9 | #include <algorithm> |
erg@chromium.org | a752852 | 2010-07-16 02:23:23 +0900 | [diff] [blame] | 10 | #include <map> |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 11 | |
| 12 | #include "base/basictypes.h" |
jhawkins@chromium.org | 22d6756 | 2011-10-13 05:26:50 +0900 | [diff] [blame] | 13 | #include "base/bind.h" |
jhawkins@chromium.org | f7b5ad9 | 2011-05-10 09:37:40 +0900 | [diff] [blame] | 14 | #include "base/callback_old.h" |
ajwong@chromium.org | 8e2e300 | 2011-09-22 03:05:41 +0900 | [diff] [blame] | 15 | #include "base/location.h" |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 16 | #include "base/logging.h" |
levin@chromium.org | 5c52868 | 2011-03-28 10:54:15 +0900 | [diff] [blame] | 17 | #include "base/memory/ref_counted.h" |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 18 | #include "base/message_loop.h" |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 19 | #include "base/message_loop_proxy.h" |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 20 | #include "base/observer_list.h" |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 21 | #include "base/task.h" |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 22 | #include "base/threading/platform_thread.h" |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 23 | |
| 24 | /////////////////////////////////////////////////////////////////////////////// |
| 25 | // |
| 26 | // OVERVIEW: |
| 27 | // |
| 28 | // A thread-safe container for a list of observers. |
maruel@chromium.org | 8fe7adc | 2009-03-04 00:01:12 +0900 | [diff] [blame] | 29 | // This is similar to the observer_list (see observer_list.h), but it |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 30 | // is more robust for multi-threaded situations. |
maruel@chromium.org | 8fe7adc | 2009-03-04 00:01:12 +0900 | [diff] [blame] | 31 | // |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 32 | // 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.org | aed4e58 | 2010-06-26 06:30:38 +0900 | [diff] [blame] | 36 | // * Any thread may trigger a notification via Notify(). |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 37 | // * 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.org | 8fe7adc | 2009-03-04 00:01:12 +0900 | [diff] [blame] | 51 | // which uses the ThreadSafeObserver. When Notifying the observers, |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 52 | // we simply call PostTask to each registered thread, and then each thread |
| 53 | // will notify its regular ObserverList. |
| 54 | // |
| 55 | /////////////////////////////////////////////////////////////////////////////// |
akalin@chromium.org | e43be3d | 2011-01-12 06:19:54 +0900 | [diff] [blame] | 56 | |
| 57 | // Forward declaration for ObserverListThreadSafeTraits. |
| 58 | template <class ObserverType> |
| 59 | class 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. |
| 70 | template <class T> |
| 71 | struct ObserverListThreadSafeTraits { |
| 72 | static void Destruct(const ObserverListThreadSafe<T>* x) { |
| 73 | delete x; |
| 74 | } |
| 75 | }; |
| 76 | |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 77 | template <class ObserverType> |
maruel@chromium.org | 8fe7adc | 2009-03-04 00:01:12 +0900 | [diff] [blame] | 78 | class ObserverListThreadSafe |
akalin@chromium.org | e43be3d | 2011-01-12 06:19:54 +0900 | [diff] [blame] | 79 | : public base::RefCountedThreadSafe< |
| 80 | ObserverListThreadSafe<ObserverType>, |
| 81 | ObserverListThreadSafeTraits<ObserverType> > { |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 82 | public: |
willchan@chromium.org | 4c7f03e | 2010-09-01 07:54:21 +0900 | [diff] [blame] | 83 | 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 89 | |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 90 | // Add an observer to the list. An observer should not be added to |
| 91 | // the same list more than once. |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 92 | void AddObserver(ObserverType* obs) { |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 93 | // 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 98 | ObserverList<ObserverType>* list = NULL; |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 99 | base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 100 | { |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 101 | base::AutoLock lock(list_lock_); |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 102 | 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 105 | } |
| 106 | list->AddObserver(obs); |
| 107 | } |
| 108 | |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 109 | // Remove an observer from the list if it is in the list. |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 110 | // If there are pending notifications in-transit to the observer, they will |
| 111 | // be aborted. |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 112 | // If the observer to be removed is in the list, RemoveObserver MUST |
| 113 | // be called from the same thread which called AddObserver. |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 114 | void RemoveObserver(ObserverType* obs) { |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 115 | ObserverListContext* context = NULL; |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 116 | ObserverList<ObserverType>* list = NULL; |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 117 | base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 118 | { |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 119 | base::AutoLock lock(list_lock_); |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 120 | typename ObserversListMap::iterator it = observer_lists_.find(thread_id); |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 121 | if (it == observer_lists_.end()) { |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 122 | // This will happen if we try to remove an observer on a thread |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 123 | // we never added an observer for. |
mbelshe@google.com | 2d95cab | 2008-11-04 02:15:07 +0900 | [diff] [blame] | 124 | return; |
| 125 | } |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 126 | context = it->second; |
| 127 | list = &context->list; |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 128 | |
| 129 | // If we're about to remove the last observer from the list, |
| 130 | // then we can remove this observer_list entirely. |
akalin@chromium.org | cbec5fb | 2011-06-05 16:07:12 +0900 | [diff] [blame] | 131 | if (list->HasObserver(obs) && list->size() == 1) |
| 132 | observer_lists_.erase(it); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 133 | } |
| 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.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 140 | delete context; |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 141 | } |
| 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.org | 59a0fef | 2011-06-17 07:32:14 +0900 | [diff] [blame] | 155 | void Notify(Method m, const A& a) { |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 156 | UnboundMethod<ObserverType, Method, Tuple1<A> > method(m, MakeTuple(a)); |
| 157 | Notify<Method, Tuple1<A> >(method); |
| 158 | } |
| 159 | |
akalin@chromium.org | 59a0fef | 2011-06-17 07:32:14 +0900 | [diff] [blame] | 160 | 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 181 | // TODO(mbelshe): Add more wrappers for Notify() with more arguments. |
| 182 | |
| 183 | private: |
akalin@chromium.org | e43be3d | 2011-01-12 06:19:54 +0900 | [diff] [blame] | 184 | // See comment above ObserverListThreadSafeTraits' definition. |
| 185 | friend struct ObserverListThreadSafeTraits<ObserverType>; |
| 186 | |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 187 | struct ObserverListContext { |
| 188 | explicit ObserverListContext(NotificationType type) |
nduca@chromium.org | ba04861 | 2011-08-16 05:33:46 +0900 | [diff] [blame] | 189 | : loop(base::MessageLoopProxy::current()), |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 190 | 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.org | 982d9d5 | 2011-01-11 05:40:36 +0900 | [diff] [blame] | 199 | ~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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 206 | template <class Method, class Params> |
| 207 | void Notify(const UnboundMethod<ObserverType, Method, Params>& method) { |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 208 | base::AutoLock lock(list_lock_); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 209 | typename ObserversListMap::iterator it; |
| 210 | for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it) { |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 211 | ObserverListContext* context = (*it).second; |
| 212 | context->loop->PostTask( |
willchan@chromium.org | d52d47f | 2010-02-13 06:38:37 +0900 | [diff] [blame] | 213 | FROM_HERE, |
jhawkins@chromium.org | 22d6756 | 2011-10-13 05:26:50 +0900 | [diff] [blame] | 214 | base::Bind(&ObserverListThreadSafe<ObserverType>:: |
| 215 | template NotifyWrapper<Method, Params>, this, context, method)); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 216 | } |
| 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.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 223 | void NotifyWrapper(ObserverListContext* context, |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 224 | const UnboundMethod<ObserverType, Method, Params>& method) { |
| 225 | |
| 226 | // Check that this list still needs notifications. |
| 227 | { |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 228 | base::AutoLock lock(list_lock_); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 229 | typename ObserversListMap::iterator it = |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 230 | observer_lists_.find(base::PlatformThread::CurrentId()); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 231 | |
| 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.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 236 | if (it == observer_lists_.end() || it->second != context) |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 237 | return; |
| 238 | } |
| 239 | |
| 240 | { |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 241 | typename ObserverList<ObserverType>::Iterator it(context->list); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 242 | 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.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 248 | if (context->list.size() == 0) { |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 249 | { |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 250 | base::AutoLock lock(list_lock_); |
willchan@chromium.org | 2777ca5 | 2010-09-18 04:33:06 +0900 | [diff] [blame] | 251 | // 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 254 | typename ObserversListMap::iterator it = |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 255 | observer_lists_.find(base::PlatformThread::CurrentId()); |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 256 | if (it != observer_lists_.end() && it->second == context) |
willchan@chromium.org | 2777ca5 | 2010-09-18 04:33:06 +0900 | [diff] [blame] | 257 | observer_lists_.erase(it); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 258 | } |
adamk@chromium.org | 2bc73e7 | 2011-08-10 06:29:59 +0900 | [diff] [blame] | 259 | delete context; |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 260 | } |
| 261 | } |
| 262 | |
rsesek@chromium.org | 1aab77c | 2011-11-24 05:34:04 +0900 | [diff] [blame^] | 263 | // 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.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 268 | |
brettw@chromium.org | abe477a | 2011-01-21 13:55:52 +0900 | [diff] [blame] | 269 | base::Lock list_lock_; // Protects the observer_lists_. |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 270 | ObserversListMap observer_lists_; |
willchan@chromium.org | 4c7f03e | 2010-09-01 07:54:21 +0900 | [diff] [blame] | 271 | const NotificationType type_; |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 272 | |
tfarina@chromium.org | 0aad842 | 2010-06-05 01:13:51 +0900 | [diff] [blame] | 273 | DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe); |
mbelshe@google.com | 877d203 | 2008-10-23 08:09:21 +0900 | [diff] [blame] | 274 | }; |
| 275 | |
| 276 | #endif // BASE_OBSERVER_LIST_THREADSAFE_H_ |