diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/threads/nsTimerImpl.h | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h new file mode 100644 index 0000000000..4065dc0100 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.h @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef nsTimerImpl_h___ +#define nsTimerImpl_h___ + +#include "nsITimer.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" + +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "mozilla/Logging.h" + +extern mozilla::LogModule* GetTimerLog(); + +#define NS_TIMER_CID \ + { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \ + 0x5ff24248, 0x1dd2, 0x11b2, { \ + 0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8 \ + } \ + } + +class nsIObserver; +class nsTimerImplHolder; + +namespace mozilla { +class LogModule; +} + +// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has +// a separate lifecycle so we can Cancel() the underlying timer when the user of +// the nsTimer has let go of its last reference. +class nsTimerImpl { + ~nsTimerImpl() { + MOZ_ASSERT(!mHolder); + + // The nsITimer interface requires that its users keep a reference to the + // timers they use while those timers are initialized but have not yet + // fired. If this assert ever fails, it is a bug in the code that created + // and used the timer. + // + // Further, note that this should never fail even with a misbehaving user, + // because nsTimer::Release checks for a refcount of 1 with an armed timer + // (a timer whose only reference is from the timer thread) and when it hits + // this will remove the timer from the timer thread and thus destroy the + // last reference, preventing this situation from occurring. + MOZ_ASSERT( + mCallback.is<UnknownCallback>() || mEventTarget->IsOnCurrentThread(), + "Must not release mCallback off-target without canceling"); + } + + public: + typedef mozilla::TimeStamp TimeStamp; + + nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl) + NS_DECL_NON_VIRTUAL_NSITIMER + + static nsresult Startup(); + static void Shutdown(); + + void SetDelayInternal(uint32_t aDelay, TimeStamp aBase = TimeStamp::Now()); + void CancelImpl(bool aClearITimer); + + void Fire(int32_t aGeneration); + + int32_t GetGeneration() { return mGeneration; } + + struct UnknownCallback {}; + + using InterfaceCallback = nsCOMPtr<nsITimerCallback>; + + using ObserverCallback = nsCOMPtr<nsIObserver>; + + /// A raw function pointer and its closed-over state, along with its name for + /// logging purposes. + struct FuncCallback { + nsTimerCallbackFunc mFunc; + void* mClosure; + const char* mName; + }; + + /// A callback defined by an owned closure and its name for logging purposes. + struct ClosureCallback { + std::function<void(nsITimer*)> mFunc; + const char* mName; + }; + + using Callback = + mozilla::Variant<UnknownCallback, InterfaceCallback, ObserverCallback, + FuncCallback, ClosureCallback>; + + nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback, + const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + Callback& GetCallback() MOZ_REQUIRES(mMutex) { + mMutex.AssertCurrentThreadOwns(); + return mCallback; + } + + bool IsRepeating() const { + static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK, + "invalid ordering of timer types!"); + static_assert( + nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE, + "invalid ordering of timer types!"); + static_assert(nsITimer::TYPE_REPEATING_PRECISE < + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "invalid ordering of timer types!"); + return mType >= nsITimer::TYPE_REPEATING_SLACK && + mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY; + } + + bool IsLowPriority() const { + return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + bool IsSlack() const { + return mType == nsITimer::TYPE_REPEATING_SLACK || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + void GetName(nsACString& aName, const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + void GetName(nsACString& aName); + + void SetHolder(nsTimerImplHolder* aHolder); + + nsCOMPtr<nsIEventTarget> mEventTarget; + + void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay); + + nsresult InitWithClosureCallback(std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString); + + // This weak reference must be cleared by the nsTimerImplHolder by + // calling SetHolder(nullptr) before the holder is destroyed. Take() + // also sets this to null, to indicate it's no longer in the + // TimerThread's list. This Take() call is NOT made under the + // nsTimerImpl's mutex (all other SetHolder calls are under the mutex, + // and all references other than in the constructor or destructor of + // nsTimerImpl). However, ALL uses and references to the holder are + // under the TimerThread's Monitor lock, so consistency is guaranteed by that. + nsTimerImplHolder* mHolder; + + // These members are set by the initiating thread, when the timer's type is + // changed and during the period where it fires on that thread. + uint8_t mType; + + // The generation number of this timer, re-generated each time the timer is + // initialized so one-shot timers can be canceled and re-initialized by the + // arming thread without any bad race conditions. + // Updated only after this timer has been removed from the timer thread. + int32_t mGeneration; + + mozilla::TimeDuration mDelay MOZ_GUARDED_BY(mMutex); + // Never updated while in the TimerThread's timer list. Only updated + // before adding to that list or during nsTimerImpl::Fire(), when it has + // been removed from the TimerThread's list. TimerThread can access + // mTimeout of any timer in the list safely + mozilla::TimeStamp mTimeout; + + RefPtr<nsITimer> mITimer MOZ_GUARDED_BY(mMutex); + mozilla::Mutex mMutex; + Callback mCallback MOZ_GUARDED_BY(mMutex); + // Counter because in rare cases we can Fire reentrantly + unsigned int mFiring MOZ_GUARDED_BY(mMutex); + + static mozilla::StaticMutex sDeltaMutex; + static double sDeltaSum MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaSumSquared MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaNum MOZ_GUARDED_BY(sDeltaMutex); +}; + +class nsTimer final : public nsITimer { + explicit nsTimer(nsIEventTarget* aTarget) + : mImpl(new nsTimerImpl(this, aTarget)) {} + + virtual ~nsTimer(); + + public: + friend class TimerThread; + friend class nsTimerEvent; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSITIMER(mImpl); + + // NOTE: This constructor is not exposed on `nsITimer` as NS_FORWARD_SAFE_ + // does not support forwarding rvalue references. + nsresult InitWithClosureCallback(std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString) { + return mImpl ? mImpl->InitWithClosureCallback(std::move(aCallback), aDelay, + aType, aNameString) + : NS_ERROR_NULL_POINTER; + } + + // Create a timer targeting the given target. nullptr indicates that the + // current thread should be used as the timer's target. + static RefPtr<nsTimer> WithEventTarget(nsIEventTarget* aTarget); + + static nsresult XPCOMConstructor(REFNSIID aIID, void** aResult); + + private: + // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will + // null this to break the cycle. + RefPtr<nsTimerImpl> mImpl; +}; + +// A class that holds on to an nsTimerImpl. This lets the nsTimerImpl object +// directly instruct its holder to forget the timer, avoiding list lookups. +class nsTimerImplHolder { + public: + explicit nsTimerImplHolder(nsTimerImpl* aTimerImpl) : mTimerImpl(aTimerImpl) { + if (mTimerImpl) { + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetHolder(this); + } + } + + ~nsTimerImplHolder() { + if (mTimerImpl) { + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetHolder(nullptr); + } + } + + void Forget(nsTimerImpl* aTimerImpl) { + if (MOZ_UNLIKELY(!mTimerImpl)) { + return; + } + MOZ_ASSERT(aTimerImpl == mTimerImpl); + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetHolder(nullptr); + mTimerImpl = nullptr; + } + + protected: + RefPtr<nsTimerImpl> mTimerImpl; +}; + +#endif /* nsTimerImpl_h___ */ |