blob: a47a40743ee92f1ab6dae8e4d6cf0036a9b0e433 [file] [log] [blame]
// Copyright (c) 2012 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 BASE_OBSERVER_LIST_THREADSAFE_H_
#define BASE_OBSERVER_LIST_THREADSAFE_H_
#include <unordered_map>
#include "base/base_export.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/synchronization/lock.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_local.h"
#include "build/build_config.h"
// TODO(fdoray): Removing these includes causes IWYU failures in other headers,
// remove them in a follow- up CL.
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
///////////////////////////////////////////////////////////////////////////////
//
// OVERVIEW:
//
// A thread-safe container for a list of observers. This is similar to the
// observer_list (see observer_list.h), but it is more robust for multi-
// threaded situations.
//
// The following use cases are supported:
// * Observers can register for notifications from any sequence. They are
// always notified on the sequence from which they were registered.
// * Any sequence may trigger a notification via Notify().
// * Observers can remove themselves from the observer list inside of a
// callback.
// * If one sequence is notifying observers concurrently with an observer
// removing itself from the observer list, the notifications will be
// silently dropped.
//
// The drawback of the threadsafe observer list is that notifications are not
// as real-time as the non-threadsafe version of this class. Notifications
// will always be done via PostTask() to another sequence, whereas with the
// non-thread-safe observer_list, notifications happen synchronously.
//
///////////////////////////////////////////////////////////////////////////////
namespace base {
namespace internal {
class BASE_EXPORT ObserverListThreadSafeBase
: public RefCountedThreadSafe<ObserverListThreadSafeBase> {
public:
ObserverListThreadSafeBase() = default;
protected:
template <typename ObserverType, typename Method>
struct Dispatcher;
template <typename ObserverType, typename ReceiverType, typename... Params>
struct Dispatcher<ObserverType, void (ReceiverType::*)(Params...)> {
static void Run(void (ReceiverType::*m)(Params...),
Params... params,
ObserverType* obj) {
(obj->*m)(std::forward<Params>(params)...);
}
};
struct NotificationDataBase {
NotificationDataBase(void* observer_list_in, const Location& from_here_in)
: observer_list(observer_list_in), from_here(from_here_in) {}
void* observer_list;
Location from_here;
};
virtual ~ObserverListThreadSafeBase() = default;
static LazyInstance<ThreadLocalPointer<const NotificationDataBase>>::Leaky
tls_current_notification_;
private:
friend class RefCountedThreadSafe<ObserverListThreadSafeBase>;
DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafeBase);
};
} // namespace internal
template <class ObserverType>
class ObserverListThreadSafe : public internal::ObserverListThreadSafeBase {
public:
ObserverListThreadSafe() = default;
explicit ObserverListThreadSafe(ObserverListPolicy policy)
: policy_(policy) {}
// Adds |observer| to the list. |observer| must not already be in the list.
void AddObserver(ObserverType* observer) {
// TODO(fdoray): Change this to a DCHECK once all call sites have a
// SequencedTaskRunnerHandle.
if (!SequencedTaskRunnerHandle::IsSet())
return;
AutoLock auto_lock(lock_);
// Add |observer| to the list of observers.
DCHECK(!ContainsKey(observers_, observer));
const scoped_refptr<SequencedTaskRunner> task_runner =
SequencedTaskRunnerHandle::Get();
observers_[observer] = task_runner;
// If this is called while a notification is being dispatched on this thread
// and |policy_| is ALL, |observer| must be notified (if a notification is
// being dispatched on another thread in parallel, the notification may or
// may not make it to |observer| depending on the outcome of the race to
// |lock_|).
if (policy_ == ObserverListPolicy::ALL) {
const NotificationDataBase* current_notification =
tls_current_notification_.Get().Get();
if (current_notification && current_notification->observer_list == this) {
task_runner->PostTask(
current_notification->from_here,
BindOnce(
&ObserverListThreadSafe<ObserverType>::NotifyWrapper, this,
observer,
*static_cast<const NotificationData*>(current_notification)));
}
}
}
// Remove an observer from the list if it is in the list.
//
// If a notification was sent to the observer but hasn't started to run yet,
// it will be aborted. If a notification has started to run, removing the
// observer won't stop it.
void RemoveObserver(ObserverType* observer) {
AutoLock auto_lock(lock_);
observers_.erase(observer);
}
// Verifies that the list is currently empty (i.e. there are no observers).
void AssertEmpty() const {
#if DCHECK_IS_ON()
AutoLock auto_lock(lock_);
DCHECK(observers_.empty());
#endif
}
// Asynchronously invokes a callback on all observers, on their registration
// sequence. You cannot assume that at the completion of the Notify call that
// all Observers have been Notified. The notification may still be pending
// delivery.
template <typename Method, typename... Params>
void Notify(const Location& from_here, Method m, Params&&... params) {
Callback<void(ObserverType*)> method =
Bind(&Dispatcher<ObserverType, Method>::Run, m,
std::forward<Params>(params)...);
AutoLock lock(lock_);
for (const auto& observer : observers_) {
observer.second->PostTask(
from_here,
BindOnce(&ObserverListThreadSafe<ObserverType>::NotifyWrapper, this,
observer.first, NotificationData(this, from_here, method)));
}
}
private:
friend class RefCountedThreadSafe<ObserverListThreadSafeBase>;
struct NotificationData : public NotificationDataBase {
NotificationData(ObserverListThreadSafe* observer_list_in,
const Location& from_here_in,
const Callback<void(ObserverType*)>& method_in)
: NotificationDataBase(observer_list_in, from_here_in),
method(method_in) {}
Callback<void(ObserverType*)> method;
};
~ObserverListThreadSafe() = default;
void NotifyWrapper(ObserverType* observer,
const NotificationData& notification) {
{
AutoLock auto_lock(lock_);
// Check whether the observer still needs a notification.
auto it = observers_.find(observer);
if (it == observers_.end())
return;
DCHECK(it->second->RunsTasksInCurrentSequence());
}
// Keep track of the notification being dispatched on the current thread.
// This will be used if the callback below calls AddObserver().
//
// Note: |tls_current_notification_| may not be nullptr if this runs in a
// nested loop started by a notification callback. In that case, it is
// important to save the previous value to restore it later.
auto& tls_current_notification = tls_current_notification_.Get();
const NotificationDataBase* const previous_notification =
tls_current_notification.Get();
tls_current_notification.Set(&notification);
// Invoke the callback.
notification.method.Run(observer);
// Reset the notification being dispatched on the current thread to its
// previous value.
tls_current_notification.Set(previous_notification);
}
const ObserverListPolicy policy_ = ObserverListPolicy::ALL;
// Synchronizes access to |observers_|.
mutable Lock lock_;
// Keys are observers. Values are the SequencedTaskRunners on which they must
// be notified.
std::unordered_map<ObserverType*, scoped_refptr<SequencedTaskRunner>>
observers_;
DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe);
};
} // namespace base
#endif // BASE_OBSERVER_LIST_THREADSAFE_H_