From 08da9456161526948f82fc117e9af5678c527fcf Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 18 Mar 2015 14:27:49 -0700 Subject: [PATCH] Bug 1144486 - Implement state watching machinery. r=jww --- dom/media/MediaDecoderReader.cpp | 10 ++ dom/media/StateWatching.h | 262 +++++++++++++++++++++++++++++++ dom/media/moz.build | 1 + 3 files changed, 273 insertions(+) create mode 100644 dom/media/StateWatching.h diff --git a/dom/media/MediaDecoderReader.cpp b/dom/media/MediaDecoderReader.cpp index c150aca94e95..309a599f590a 100644 --- a/dom/media/MediaDecoderReader.cpp +++ b/dom/media/MediaDecoderReader.cpp @@ -35,6 +35,7 @@ extern PRLogModuleInfo* gMediaDecoderLog; PRLogModuleInfo* gMediaPromiseLog; +PRLogModuleInfo* gStateWatchingLog; void EnsureMediaPromiseLog() @@ -44,6 +45,14 @@ EnsureMediaPromiseLog() } } +void +EnsureStateWatchingLog() +{ + if (!gStateWatchingLog) { + gStateWatchingLog = PR_NewLogModule("StateWatching"); + } +} + class VideoQueueMemoryFunctor : public nsDequeFunctor { public: VideoQueueMemoryFunctor() : mSize(0) {} @@ -88,6 +97,7 @@ MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) { MOZ_COUNT_CTOR(MediaDecoderReader); EnsureMediaPromiseLog(); + EnsureStateWatchingLog(); } MediaDecoderReader::~MediaDecoderReader() diff --git a/dom/media/StateWatching.h b/dom/media/StateWatching.h new file mode 100644 index 000000000000..5022ca7577e6 --- /dev/null +++ b/dom/media/StateWatching.h @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(StateWatching_h_) +#define StateWatching_h_ + +#include "AbstractThread.h" + +#include "mozilla/UniquePtr.h" +#include "mozilla/unused.h" + +#include "nsISupportsImpl.h" + +/* + * The state-watching machinery automates the process of responding to changes + * in various pieces of state. + * + * A standard programming pattern is as follows: + * + * mFoo = ...; + * NotifyStuffChanged(); + * ... + * mBar = ...; + * NotifyStuffChanged(); + * + * This pattern is error-prone and difficult to audit because it requires the + * programmer to manually trigger the update routine. This can be especially + * problematic when the update routine depends on numerous pieces of state, and + * when that state is modified across a variety of helper methods. In these + * cases the responsibility for invoking the routine is often unclear, causing + * developers to scatter calls to it like pixie dust. This can result in + * duplicate invocations (which is wasteful) and missing invocations in corner- + * cases (which is a source of bugs). + * + * This file provides a set of primitives that automatically handle updates and + * allow the programmers to explicitly construct a graph of state dependencies. + * When used correctly, it eliminates the guess-work and wasted cycles described + * above. + * + * There are two basic pieces: + * (1) Objects that can be watched for updates. These inherit WatchTarget. + * (2) Objects that receive objects and trigger processing. These inherit + * AbstractWatcher. Note that some watchers may themselves be watched, + * allowing watchers to be composed together. + * + * Note that none of this machinery is thread-safe - it must all happen on the + * same owning thread. To solve multi-threaded use-cases, use state mirroring + * and watch the mirrored value. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +extern PRLogModuleInfo* gStateWatchingLog; + +void EnsureStateWatchingLog(); + +#define WATCH_LOG(x, ...) \ + MOZ_ASSERT(gStateWatchingLog); \ + PR_LOG(gStateWatchingLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__)) + +/* + * AbstractWatcher is a superclass from which all watchers must inherit. + */ +class AbstractWatcher +{ +public: + NS_INLINE_DECL_REFCOUNTING(AbstractWatcher) + AbstractWatcher() : mDestroyed(false) {} + bool IsDestroyed() { return mDestroyed; } + virtual void Notify() = 0; + +protected: + virtual ~AbstractWatcher() {} + virtual void CustomDestroy() {} + +private: + // Only the holder is allowed to invoke Destroy(). + friend class WatcherHolder; + void Destroy() + { + mDestroyed = true; + CustomDestroy(); + } + + bool mDestroyed; +}; + +/* + * WatchTarget is a superclass from which all watchable things must inherit. + * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass + * needs only to invoke NotifyWatchers when something changes. + * + * The functionality that this class provides is not threadsafe, and should only + * be used on the thread that owns that WatchTarget. + */ +class WatchTarget +{ +public: + explicit WatchTarget(const char* aName) : mName(aName) {} + + void AddWatcher(AbstractWatcher* aWatcher) + { + MOZ_ASSERT(!mWatchers.Contains(aWatcher)); + mWatchers.AppendElement(aWatcher); + aWatcher->Notify(); + } + + void RemoveWatcher(AbstractWatcher* aWatcher) + { + MOZ_ASSERT(mWatchers.Contains(aWatcher)); + mWatchers.RemoveElement(aWatcher); + } + +protected: + void NotifyWatchers() + { + WATCH_LOG("%s[%p] notifying watchers\n", mName, this); + PruneWatchers(); + for (size_t i = 0; i < mWatchers.Length(); ++i) { + mWatchers[i]->Notify(); + } + } + +private: + // We don't have Watchers explicitly unregister themselves when they die, + // because then they'd need back-references to all the WatchTargets they're + // subscribed to, and WatchTargets aren't reference-counted. So instead we + // just prune dead ones at appropriate times, which works just fine. + void PruneWatchers() + { + for (int i = mWatchers.Length() - 1; i >= 0; --i) { + if (mWatchers[i]->IsDestroyed()) { + mWatchers.RemoveElementAt(i); + } + } + } + + nsTArray> mWatchers; + const char* mName; +}; + +/* + * Watchable is a wrapper class that turns any primitive into a WatchTarget. + */ +template +class Watchable : public WatchTarget +{ +public: + Watchable(const T& aInitialValue, const char* aName) + : WatchTarget(aName), mValue(aInitialValue) {} + + operator const T&() const { return mValue; } + Watchable& operator=(const T& aNewValue) + { + if (aNewValue != mValue) { + mValue = aNewValue; + NotifyWatchers(); + } + + return *this; + } + +private: + Watchable(const Watchable& aOther); // Not implemented + Watchable& operator=(const Watchable& aOther); // Not implemented + + T mValue; +}; + +/* + * Watcher is the concrete AbstractWatcher implementation. It registers itself with + * various WatchTargets, and accepts any number of callbacks that will be + * invoked whenever any WatchTarget notifies of a change. It may also be watched + * by other watchers. + * + * When a notification is received, a runnable is passed as "direct" to the + * thread's tail dispatcher, which run directly (rather than via dispatch) when + * the tail dispatcher fires. All subsequent notifications are ignored until the + * runnable executes, triggering the updates and resetting the flags. + */ +class Watcher : public AbstractWatcher, public WatchTarget +{ +public: + explicit Watcher(const char* aName) + : WatchTarget(aName), mNotifying(false) {} + + void Notify() override + { + if (mNotifying) { + return; + } + mNotifying = true; + + // Queue up our notification jobs to run in a stable state. + nsCOMPtr r = NS_NewRunnableMethod(this, &Watcher::DoNotify); + AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget()); + } + + void Watch(WatchTarget& aTarget) { aTarget.AddWatcher(this); } + void Unwatch(WatchTarget& aTarget) { aTarget.RemoveWatcher(this); } + + template + void AddWeakCallback(ThisType* aThisVal, void(ThisType::*aMethod)()) + { + mCallbacks.AppendElement(NS_NewNonOwningRunnableMethod(aThisVal, aMethod)); + } + +protected: + void CustomDestroy() override { mCallbacks.Clear(); } + + void DoNotify() + { + MOZ_ASSERT(mNotifying); + mNotifying = false; + + // Notify dependent watchers. + NotifyWatchers(); + + for (size_t i = 0; i < mCallbacks.Length(); ++i) { + mCallbacks[i]->Run(); + } + } + +private: + nsTArray> mCallbacks; + + bool mNotifying; +}; + +/* + * WatcherHolder encapsulates a Watcher and handles lifetime issues. Use it to + * holder watcher members. + */ +class WatcherHolder +{ +public: + explicit WatcherHolder(const char* aName) { mWatcher = new Watcher(aName); } + operator Watcher*() { return mWatcher; } + Watcher* operator->() { return mWatcher; } + + ~WatcherHolder() + { + mWatcher->Destroy(); + mWatcher = nullptr; + } + +private: + nsRefPtr mWatcher; +}; + + +#undef WATCH_LOG + +} // namespace mozilla + +#endif diff --git a/dom/media/moz.build b/dom/media/moz.build index 5b00e3e8fbb9..db4d7dc80639 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -137,6 +137,7 @@ EXPORTS += [ 'SelfRef.h', 'SharedBuffer.h', 'SharedThreadPool.h', + 'StateWatching.h', 'StreamBuffer.h', 'TaskDispatcher.h', 'ThreadPoolCOMListener.h',