diff options
Diffstat (limited to 'xpcom/build/IOInterposer.cpp')
-rw-r--r-- | xpcom/build/IOInterposer.cpp | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp new file mode 100644 index 000000000..1c3ff54d5 --- /dev/null +++ b/xpcom/build/IOInterposer.cpp @@ -0,0 +1,582 @@ +/* -*- 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/. */ + +#include <algorithm> +#include <vector> + +#include "IOInterposer.h" + +#include "IOInterposerPrivate.h" +#include "MainThreadIOLogger.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#if !defined(XP_WIN) +#include "NSPRInterposer.h" +#endif // !defined(XP_WIN) +#include "nsXULAppAPI.h" +#include "PoisonIOInterposer.h" + +using namespace mozilla; + +namespace { + +/** Find if a vector contains a specific element */ +template<class T> +bool +VectorContains(const std::vector<T>& aVector, const T& aElement) +{ + return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end(); +} + +/** Remove element from a vector */ +template<class T> +void +VectorRemove(std::vector<T>& aVector, const T& aElement) +{ + typename std::vector<T>::iterator newEnd = + std::remove(aVector.begin(), aVector.end(), aElement); + aVector.erase(newEnd, aVector.end()); +} + +/** Lists of Observers */ +struct ObserverLists +{ +private: + ~ObserverLists() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists) + + ObserverLists() {} + + ObserverLists(ObserverLists const& aOther) + : mCreateObservers(aOther.mCreateObservers) + , mReadObservers(aOther.mReadObservers) + , mWriteObservers(aOther.mWriteObservers) + , mFSyncObservers(aOther.mFSyncObservers) + , mStatObservers(aOther.mStatObservers) + , mCloseObservers(aOther.mCloseObservers) + , mStageObservers(aOther.mStageObservers) + { + } + // Lists of observers for I/O events. + // These are implemented as vectors since they are allowed to survive gecko, + // without reporting leaks. This is necessary for the IOInterposer to be used + // for late-write checks. + std::vector<IOInterposeObserver*> mCreateObservers; + std::vector<IOInterposeObserver*> mReadObservers; + std::vector<IOInterposeObserver*> mWriteObservers; + std::vector<IOInterposeObserver*> mFSyncObservers; + std::vector<IOInterposeObserver*> mStatObservers; + std::vector<IOInterposeObserver*> mCloseObservers; + std::vector<IOInterposeObserver*> mStageObservers; +}; + +class PerThreadData +{ +public: + explicit PerThreadData(bool aIsMainThread = false) + : mIsMainThread(aIsMainThread) + , mIsHandlingObservation(false) + , mCurrentGeneration(0) + { + MOZ_COUNT_CTOR(PerThreadData); + } + + ~PerThreadData() + { + MOZ_COUNT_DTOR(PerThreadData); + } + + void CallObservers(IOInterposeObserver::Observation& aObservation) + { + // Prevent recursive reporting. + if (mIsHandlingObservation) { + return; + } + + mIsHandlingObservation = true; + // Decide which list of observers to inform + std::vector<IOInterposeObserver*>* observers = nullptr; + switch (aObservation.ObservedOperation()) { + case IOInterposeObserver::OpCreateOrOpen: + observers = &mObserverLists->mCreateObservers; + break; + case IOInterposeObserver::OpRead: + observers = &mObserverLists->mReadObservers; + break; + case IOInterposeObserver::OpWrite: + observers = &mObserverLists->mWriteObservers; + break; + case IOInterposeObserver::OpFSync: + observers = &mObserverLists->mFSyncObservers; + break; + case IOInterposeObserver::OpStat: + observers = &mObserverLists->mStatObservers; + break; + case IOInterposeObserver::OpClose: + observers = &mObserverLists->mCloseObservers; + break; + case IOInterposeObserver::OpNextStage: + observers = &mObserverLists->mStageObservers; + break; + default: { + // Invalid IO operation, see documentation comment for + // IOInterposer::Report() + MOZ_ASSERT(false); + // Just ignore it in non-debug builds. + return; + } + } + MOZ_ASSERT(observers); + + // Inform observers + for (auto i = observers->begin(), e = observers->end(); i != e; ++i) { + (*i)->Observe(aObservation); + } + mIsHandlingObservation = false; + } + + inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; } + + inline bool IsMainThread() const { return mIsMainThread; } + + inline void SetObserverLists(uint32_t aNewGeneration, + RefPtr<ObserverLists>& aNewLists) + { + mCurrentGeneration = aNewGeneration; + mObserverLists = aNewLists; + } + + inline void ClearObserverLists() + { + if (mObserverLists) { + mCurrentGeneration = 0; + mObserverLists = nullptr; + } + } + +private: + bool mIsMainThread; + bool mIsHandlingObservation; + uint32_t mCurrentGeneration; + RefPtr<ObserverLists> mObserverLists; +}; + +class MasterList +{ +public: + MasterList() + : mObservedOperations(IOInterposeObserver::OpNone) + , mIsEnabled(true) + { + MOZ_COUNT_CTOR(MasterList); + } + + ~MasterList() + { + MOZ_COUNT_DTOR(MasterList); + } + + inline void Disable() { mIsEnabled = false; } + inline void Enable() { mIsEnabled = true; } + + void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) + { + IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + // You can register to observe multiple types of observations + // but you'll never be registered twice for the same observations. + if (aOp & IOInterposeObserver::OpCreateOrOpen && + !VectorContains(newLists->mCreateObservers, aObserver)) { + newLists->mCreateObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpRead && + !VectorContains(newLists->mReadObservers, aObserver)) { + newLists->mReadObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpWrite && + !VectorContains(newLists->mWriteObservers, aObserver)) { + newLists->mWriteObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpFSync && + !VectorContains(newLists->mFSyncObservers, aObserver)) { + newLists->mFSyncObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpStat && + !VectorContains(newLists->mStatObservers, aObserver)) { + newLists->mStatObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpClose && + !VectorContains(newLists->mCloseObservers, aObserver)) { + newLists->mCloseObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpNextStage && + !VectorContains(newLists->mStageObservers, aObserver)) { + newLists->mStageObservers.push_back(aObserver); + } + mObserverLists = newLists; + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations | aOp); + + mCurrentGeneration++; + } + + void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) + { + IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + + if (aOp & IOInterposeObserver::OpCreateOrOpen) { + VectorRemove(newLists->mCreateObservers, aObserver); + if (newLists->mCreateObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpCreateOrOpen); + } + } + if (aOp & IOInterposeObserver::OpRead) { + VectorRemove(newLists->mReadObservers, aObserver); + if (newLists->mReadObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpRead); + } + } + if (aOp & IOInterposeObserver::OpWrite) { + VectorRemove(newLists->mWriteObservers, aObserver); + if (newLists->mWriteObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpWrite); + } + } + if (aOp & IOInterposeObserver::OpFSync) { + VectorRemove(newLists->mFSyncObservers, aObserver); + if (newLists->mFSyncObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpFSync); + } + } + if (aOp & IOInterposeObserver::OpStat) { + VectorRemove(newLists->mStatObservers, aObserver); + if (newLists->mStatObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpStat); + } + } + if (aOp & IOInterposeObserver::OpClose) { + VectorRemove(newLists->mCloseObservers, aObserver); + if (newLists->mCloseObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpClose); + } + } + if (aOp & IOInterposeObserver::OpNextStage) { + VectorRemove(newLists->mStageObservers, aObserver); + if (newLists->mStageObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpNextStage); + } + } + mObserverLists = newLists; + mCurrentGeneration++; + } + + void Update(PerThreadData& aPtd) + { + if (mCurrentGeneration == aPtd.GetCurrentGeneration()) { + return; + } + // If the generation counts don't match then we need to update the current + // thread's observer list with the new master list. + IOInterposer::AutoLock lock(mLock); + aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); + } + + inline bool IsObservedOperation(IOInterposeObserver::Operation aOp) + { + // The quick reader may observe that no locks are being employed here, + // hence the result of the operations is truly undefined. However, most + // computers will usually return either true or false, which is good enough. + // It is not a problem if we occasionally report more or less IO than is + // actually occurring. + return mIsEnabled && !!(mObservedOperations & aOp); + } + +private: + RefPtr<ObserverLists> mObserverLists; + // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked + // (We want to monitor IO during shutdown). Furthermore, as we may have to + // unregister observers during shutdown an OffTheBooksMutex is not an option + // either, as its base calls into sDeadlockDetector which may be nullptr + // during shutdown. + IOInterposer::Mutex mLock; + // Flags tracking which operations are being observed + IOInterposeObserver::Operation mObservedOperations; + // Used for quickly disabling everything by IOInterposer::Disable() + Atomic<bool> mIsEnabled; + // Used to inform threads that the master observer list has changed + Atomic<uint32_t> mCurrentGeneration; +}; + +// Special observation used by IOInterposer::EnteringNextStage() +class NextStageObservation : public IOInterposeObserver::Observation +{ +public: + NextStageObservation() + : IOInterposeObserver::Observation(IOInterposeObserver::OpNextStage, + "IOInterposer", false) + { + mStart = TimeStamp::Now(); + mEnd = mStart; + } +}; + +// List of observers registered +static StaticAutoPtr<MasterList> sMasterList; +static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData; +static bool sThreadLocalDataInitialized; +} // namespace + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const char* aReference, + bool aShouldReport) + : mOperation(aOperation) + , mReference(aReference) + , mShouldReport(IOInterposer::IsObservedOperation(aOperation) && + aShouldReport) +{ + if (mShouldReport) { + mStart = TimeStamp::Now(); + } +} + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const TimeStamp& aStart, + const TimeStamp& aEnd, + const char* aReference) + : mOperation(aOperation) + , mStart(aStart) + , mEnd(aEnd) + , mReference(aReference) + , mShouldReport(false) +{ +} + +const char* +IOInterposeObserver::Observation::ObservedOperationString() const +{ + switch (mOperation) { + case OpCreateOrOpen: + return "create/open"; + case OpRead: + return "read"; + case OpWrite: + return "write"; + case OpFSync: + return "fsync"; + case OpStat: + return "stat"; + case OpClose: + return "close"; + case OpNextStage: + return "NextStage"; + default: + return "unknown"; + } +} + +void +IOInterposeObserver::Observation::Report() +{ + if (mShouldReport) { + mEnd = TimeStamp::Now(); + IOInterposer::Report(*this); + } +} + +bool +IOInterposer::Init() +{ + // Don't initialize twice... + if (sMasterList) { + return true; + } + if (!sThreadLocalData.init()) { + return false; + } + sThreadLocalDataInitialized = true; + bool isMainThread = true; + RegisterCurrentThread(isMainThread); + sMasterList = new MasterList(); + + MainThreadIOLogger::Init(); + + // Now we initialize the various interposers depending on platform + InitPoisonIOInterposer(); + // We don't hook NSPR on Windows because PoisonIOInterposer captures a + // superset of the former's events. +#if !defined(XP_WIN) + InitNSPRIOInterposing(); +#endif + return true; +} + +bool +IOInterposeObserver::IsMainThread() +{ + if (!sThreadLocalDataInitialized) { + return false; + } + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + return false; + } + return ptd->IsMainThread(); +} + +void +IOInterposer::Clear() +{ + /* Clear() is a no-op on release builds so that we may continue to trap I/O + until process termination. In leak-checking builds, we need to shut down + IOInterposer so that all references are properly released. */ +#ifdef NS_FREE_PERMANENT_DATA + UnregisterCurrentThread(); + sMasterList = nullptr; +#endif +} + +void +IOInterposer::Disable() +{ + if (!sMasterList) { + return; + } + sMasterList->Disable(); +} + +void +IOInterposer::Enable() +{ + if (!sMasterList) { + return; + } + sMasterList->Enable(); +} + +void +IOInterposer::Report(IOInterposeObserver::Observation& aObservation) +{ + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + // In this case the current thread is not registered with IOInterposer. + // Alternatively we could take the slow path and just lock everything if + // we're not registered. That could potentially perform poorly, though. + return; + } + + if (!sMasterList) { + // If there is no longer a master list then we should clear the local one. + ptd->ClearObserverLists(); + return; + } + + sMasterList->Update(*ptd); + + // Don't try to report if there's nobody listening. + if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) { + return; + } + + ptd->CallObservers(aObservation); +} + +bool +IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) +{ + return sMasterList && sMasterList->IsObservedOperation(aOp); +} + +void +IOInterposer::Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) +{ + MOZ_ASSERT(aObserver); + if (!sMasterList || !aObserver) { + return; + } + + sMasterList->Register(aOp, aObserver); +} + +void +IOInterposer::Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) +{ + if (!sMasterList) { + return; + } + + sMasterList->Unregister(aOp, aObserver); +} + +void +IOInterposer::RegisterCurrentThread(bool aIsMainThread) +{ + if (!sThreadLocalDataInitialized) { + return; + } + MOZ_ASSERT(!sThreadLocalData.get()); + PerThreadData* curThreadData = new PerThreadData(aIsMainThread); + sThreadLocalData.set(curThreadData); +} + +void +IOInterposer::UnregisterCurrentThread() +{ + if (!sThreadLocalDataInitialized) { + return; + } + PerThreadData* curThreadData = sThreadLocalData.get(); + MOZ_ASSERT(curThreadData); + sThreadLocalData.set(nullptr); + delete curThreadData; +} + +void +IOInterposer::EnteringNextStage() +{ + if (!sMasterList) { + return; + } + NextStageObservation observation; + Report(observation); +} + |