michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "IOInterposer.h" michael@0: michael@0: #include "IOInterposerPrivate.h" michael@0: #include "MainThreadIOLogger.h" michael@0: #include "mozilla/Atomics.h" michael@0: #include "mozilla/Mutex.h" michael@0: #if defined(MOZILLA_INTERNAL_API) michael@0: // We need to undefine MOZILLA_INTERNAL_API for RefPtr.h because IOInterposer michael@0: // does not clean up its data before shutdown. michael@0: #undef MOZILLA_INTERNAL_API michael@0: #include "mozilla/RefPtr.h" michael@0: #define MOZILLA_INTERNAL_API michael@0: #else michael@0: #include "mozilla/RefPtr.h" michael@0: #endif // defined(MOZILLA_INTERNAL_API) michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/ThreadLocal.h" michael@0: #if !defined(XP_WIN) michael@0: #include "NSPRInterposer.h" michael@0: #endif // !defined(XP_WIN) michael@0: #include "nsXULAppAPI.h" michael@0: #include "PoisonIOInterposer.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace { michael@0: michael@0: /** Find if a vector contains a specific element */ michael@0: template michael@0: bool VectorContains(const std::vector& vector, const T& element) michael@0: { michael@0: return std::find(vector.begin(), vector.end(), element) != vector.end(); michael@0: } michael@0: michael@0: /** Remove element from a vector */ michael@0: template michael@0: void VectorRemove(std::vector& vector, const T& element) michael@0: { michael@0: typename std::vector::iterator newEnd = std::remove(vector.begin(), michael@0: vector.end(), element); michael@0: vector.erase(newEnd, vector.end()); michael@0: } michael@0: michael@0: /** Lists of Observers */ michael@0: struct ObserverLists : public mozilla::AtomicRefCounted michael@0: { michael@0: ObserverLists() michael@0: { michael@0: } michael@0: michael@0: ObserverLists(ObserverLists const & aOther) michael@0: : mCreateObservers(aOther.mCreateObservers) michael@0: , mReadObservers(aOther.mReadObservers) michael@0: , mWriteObservers(aOther.mWriteObservers) michael@0: , mFSyncObservers(aOther.mFSyncObservers) michael@0: , mStatObservers(aOther.mStatObservers) michael@0: , mCloseObservers(aOther.mCloseObservers) michael@0: , mStageObservers(aOther.mStageObservers) michael@0: { michael@0: } michael@0: // Lists of observers for I/O events. michael@0: // These are implemented as vectors since they are allowed to survive gecko, michael@0: // without reporting leaks. This is necessary for the IOInterposer to be used michael@0: // for late-write checks. michael@0: std::vector mCreateObservers; michael@0: std::vector mReadObservers; michael@0: std::vector mWriteObservers; michael@0: std::vector mFSyncObservers; michael@0: std::vector mStatObservers; michael@0: std::vector mCloseObservers; michael@0: std::vector mStageObservers; michael@0: }; michael@0: michael@0: class PerThreadData michael@0: { michael@0: public: michael@0: PerThreadData(bool aIsMainThread = false) michael@0: : mIsMainThread(aIsMainThread) michael@0: , mIsHandlingObservation(false) michael@0: , mCurrentGeneration(0) michael@0: { michael@0: } michael@0: michael@0: void michael@0: CallObservers(IOInterposeObserver::Observation& aObservation) michael@0: { michael@0: // Prevent recursive reporting. michael@0: if (mIsHandlingObservation) { michael@0: return; michael@0: } michael@0: michael@0: mIsHandlingObservation = true; michael@0: // Decide which list of observers to inform michael@0: std::vector* observers = nullptr; michael@0: switch (aObservation.ObservedOperation()) { michael@0: case IOInterposeObserver::OpCreateOrOpen: michael@0: { michael@0: observers = &mObserverLists->mCreateObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpRead: michael@0: { michael@0: observers = &mObserverLists->mReadObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpWrite: michael@0: { michael@0: observers = &mObserverLists->mWriteObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpFSync: michael@0: { michael@0: observers = &mObserverLists->mFSyncObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpStat: michael@0: { michael@0: observers = &mObserverLists->mStatObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpClose: michael@0: { michael@0: observers = &mObserverLists->mCloseObservers; michael@0: } michael@0: break; michael@0: case IOInterposeObserver::OpNextStage: michael@0: { michael@0: observers = &mObserverLists->mStageObservers; michael@0: } michael@0: break; michael@0: default: michael@0: { michael@0: // Invalid IO operation, see documentation comment for michael@0: // IOInterposer::Report() michael@0: MOZ_ASSERT(false); michael@0: // Just ignore it in non-debug builds. michael@0: return; michael@0: } michael@0: } michael@0: MOZ_ASSERT(observers); michael@0: michael@0: // Inform observers michael@0: for (std::vector::iterator i = observers->begin(), michael@0: e = observers->end(); i != e; ++i) michael@0: { michael@0: (*i)->Observe(aObservation); michael@0: } michael@0: mIsHandlingObservation = false; michael@0: } michael@0: michael@0: inline uint32_t michael@0: GetCurrentGeneration() const michael@0: { michael@0: return mCurrentGeneration; michael@0: } michael@0: michael@0: inline bool michael@0: IsMainThread() const michael@0: { michael@0: return mIsMainThread; michael@0: } michael@0: michael@0: inline void michael@0: SetObserverLists(uint32_t aNewGeneration, RefPtr& aNewLists) michael@0: { michael@0: mCurrentGeneration = aNewGeneration; michael@0: mObserverLists = aNewLists; michael@0: } michael@0: michael@0: private: michael@0: bool mIsMainThread; michael@0: bool mIsHandlingObservation; michael@0: uint32_t mCurrentGeneration; michael@0: RefPtr mObserverLists; michael@0: }; michael@0: michael@0: class MasterList michael@0: { michael@0: public: michael@0: MasterList() michael@0: : mObservedOperations(IOInterposeObserver::OpNone) michael@0: , mIsEnabled(true) michael@0: { michael@0: } michael@0: michael@0: ~MasterList() michael@0: { michael@0: } michael@0: michael@0: inline void michael@0: Disable() michael@0: { michael@0: mIsEnabled = false; michael@0: } michael@0: michael@0: void michael@0: Register(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver) michael@0: { michael@0: IOInterposer::AutoLock lock(mLock); michael@0: michael@0: ObserverLists* newLists = nullptr; michael@0: if (mObserverLists) { michael@0: newLists = new ObserverLists(*mObserverLists); michael@0: } else { michael@0: newLists = new ObserverLists(); michael@0: } michael@0: // You can register to observe multiple types of observations michael@0: // but you'll never be registered twice for the same observations. michael@0: if (aOp & IOInterposeObserver::OpCreateOrOpen && michael@0: !VectorContains(newLists->mCreateObservers, aObserver)) { michael@0: newLists->mCreateObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpRead && michael@0: !VectorContains(newLists->mReadObservers, aObserver)) { michael@0: newLists->mReadObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpWrite && michael@0: !VectorContains(newLists->mWriteObservers, aObserver)) { michael@0: newLists->mWriteObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpFSync && michael@0: !VectorContains(newLists->mFSyncObservers, aObserver)) { michael@0: newLists->mFSyncObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpStat && michael@0: !VectorContains(newLists->mStatObservers, aObserver)) { michael@0: newLists->mStatObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpClose && michael@0: !VectorContains(newLists->mCloseObservers, aObserver)) { michael@0: newLists->mCloseObservers.push_back(aObserver); michael@0: } michael@0: if (aOp & IOInterposeObserver::OpNextStage && michael@0: !VectorContains(newLists->mStageObservers, aObserver)) { michael@0: newLists->mStageObservers.push_back(aObserver); michael@0: } michael@0: mObserverLists = newLists; michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations | aOp); michael@0: michael@0: mCurrentGeneration++; michael@0: } michael@0: michael@0: void michael@0: Unregister(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver) michael@0: { michael@0: IOInterposer::AutoLock lock(mLock); michael@0: michael@0: ObserverLists* newLists = nullptr; michael@0: if (mObserverLists) { michael@0: newLists = new ObserverLists(*mObserverLists); michael@0: } else { michael@0: newLists = new ObserverLists(); michael@0: } michael@0: michael@0: if (aOp & IOInterposeObserver::OpCreateOrOpen) { michael@0: VectorRemove(newLists->mCreateObservers, aObserver); michael@0: if (newLists->mCreateObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & michael@0: ~IOInterposeObserver::OpCreateOrOpen); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpRead) { michael@0: VectorRemove(newLists->mReadObservers, aObserver); michael@0: if (newLists->mReadObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpRead); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpWrite) { michael@0: VectorRemove(newLists->mWriteObservers, aObserver); michael@0: if (newLists->mWriteObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpWrite); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpFSync) { michael@0: VectorRemove(newLists->mFSyncObservers, aObserver); michael@0: if (newLists->mFSyncObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpFSync); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpStat) { michael@0: VectorRemove(newLists->mStatObservers, aObserver); michael@0: if (newLists->mStatObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpStat); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpClose) { michael@0: VectorRemove(newLists->mCloseObservers, aObserver); michael@0: if (newLists->mCloseObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpClose); michael@0: } michael@0: } michael@0: if (aOp & IOInterposeObserver::OpNextStage) { michael@0: VectorRemove(newLists->mStageObservers, aObserver); michael@0: if (newLists->mStageObservers.empty()) { michael@0: mObservedOperations = (IOInterposeObserver::Operation) michael@0: (mObservedOperations & ~IOInterposeObserver::OpNextStage); michael@0: } michael@0: } michael@0: mObserverLists = newLists; michael@0: mCurrentGeneration++; michael@0: } michael@0: michael@0: void michael@0: Update(PerThreadData &aPtd) michael@0: { michael@0: if (mCurrentGeneration == aPtd.GetCurrentGeneration()) { michael@0: return; michael@0: } michael@0: // If the generation counts don't match then we need to update the current michael@0: // thread's observer list with the new master list. michael@0: IOInterposer::AutoLock lock(mLock); michael@0: aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); michael@0: } michael@0: michael@0: inline bool michael@0: IsObservedOperation(IOInterposeObserver::Operation aOp) michael@0: { michael@0: // The quick reader may observe that no locks are being employed here, michael@0: // hence the result of the operations is truly undefined. However, most michael@0: // computers will usually return either true or false, which is good enough. michael@0: // It is not a problem if we occasionally report more or less IO than is michael@0: // actually occurring. michael@0: return mIsEnabled && !!(mObservedOperations & aOp); michael@0: } michael@0: michael@0: private: michael@0: RefPtr mObserverLists; michael@0: // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked michael@0: // (We want to monitor IO during shutdown). Furthermore, as we may have to michael@0: // unregister observers during shutdown an OffTheBooksMutex is not an option michael@0: // either, as its base calls into sDeadlockDetector which may be nullptr michael@0: // during shutdown. michael@0: IOInterposer::Mutex mLock; michael@0: // Flags tracking which operations are being observed michael@0: IOInterposeObserver::Operation mObservedOperations; michael@0: // Used for quickly disabling everything by IOInterposer::Disable() michael@0: Atomic mIsEnabled; michael@0: // Used to inform threads that the master observer list has changed michael@0: Atomic mCurrentGeneration; michael@0: }; michael@0: michael@0: // Special observation used by IOInterposer::EnteringNextStage() michael@0: class NextStageObservation : public IOInterposeObserver::Observation michael@0: { michael@0: public: michael@0: NextStageObservation() michael@0: : IOInterposeObserver::Observation(IOInterposeObserver::OpNextStage, michael@0: "IOInterposer", false) michael@0: { michael@0: mStart = TimeStamp::Now(); michael@0: } michael@0: }; michael@0: michael@0: // List of observers registered michael@0: static StaticAutoPtr sMasterList; michael@0: static ThreadLocal sThreadLocalData; michael@0: } // anonymous namespace michael@0: michael@0: IOInterposeObserver::Observation::Observation(Operation aOperation, michael@0: const char* aReference, michael@0: bool aShouldReport) michael@0: : mOperation(aOperation) michael@0: , mReference(aReference) michael@0: , mShouldReport(IOInterposer::IsObservedOperation(aOperation) && michael@0: aShouldReport) michael@0: { michael@0: if (mShouldReport) { michael@0: mStart = TimeStamp::Now(); michael@0: } michael@0: } michael@0: michael@0: IOInterposeObserver::Observation::Observation(Operation aOperation, michael@0: const TimeStamp& aStart, michael@0: const TimeStamp& aEnd, michael@0: const char* aReference) michael@0: : mOperation(aOperation) michael@0: , mStart(aStart) michael@0: , mEnd(aEnd) michael@0: , mReference(aReference) michael@0: , mShouldReport(false) michael@0: { michael@0: } michael@0: michael@0: const char* michael@0: IOInterposeObserver::Observation::ObservedOperationString() const michael@0: { michael@0: switch(mOperation) { michael@0: case OpCreateOrOpen: michael@0: return "create/open"; michael@0: case OpRead: michael@0: return "read"; michael@0: case OpWrite: michael@0: return "write"; michael@0: case OpFSync: michael@0: return "fsync"; michael@0: case OpStat: michael@0: return "stat"; michael@0: case OpClose: michael@0: return "close"; michael@0: case OpNextStage: michael@0: return "NextStage"; michael@0: default: michael@0: return "unknown"; michael@0: } michael@0: } michael@0: michael@0: void michael@0: IOInterposeObserver::Observation::Report() michael@0: { michael@0: if (mShouldReport) { michael@0: mEnd = TimeStamp::Now(); michael@0: IOInterposer::Report(*this); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: IOInterposer::Init() michael@0: { michael@0: // Don't initialize twice... michael@0: if (sMasterList) { michael@0: return true; michael@0: } michael@0: if (!sThreadLocalData.init()) { michael@0: return false; michael@0: } michael@0: #if defined(XP_WIN) michael@0: bool isMainThread = XRE_GetWindowsEnvironment() != michael@0: WindowsEnvironmentType_Metro; michael@0: #else michael@0: bool isMainThread = true; michael@0: #endif michael@0: RegisterCurrentThread(isMainThread); michael@0: sMasterList = new MasterList(); michael@0: michael@0: MainThreadIOLogger::Init(); michael@0: michael@0: // Now we initialize the various interposers depending on platform michael@0: InitPoisonIOInterposer(); michael@0: // We don't hook NSPR on Windows because PoisonIOInterposer captures a michael@0: // superset of the former's events. michael@0: #if !defined(XP_WIN) michael@0: InitNSPRIOInterposing(); michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: IOInterposeObserver::IsMainThread() michael@0: { michael@0: if (!sThreadLocalData.initialized()) { michael@0: return false; michael@0: } michael@0: PerThreadData *ptd = sThreadLocalData.get(); michael@0: if (!ptd) { michael@0: return false; michael@0: } michael@0: return ptd->IsMainThread(); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::Clear() michael@0: { michael@0: sMasterList = nullptr; michael@0: } michael@0: michael@0: void michael@0: IOInterposer::Disable() michael@0: { michael@0: if (!sMasterList) { michael@0: return; michael@0: } michael@0: sMasterList->Disable(); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::Report(IOInterposeObserver::Observation& aObservation) michael@0: { michael@0: MOZ_ASSERT(sMasterList); michael@0: if (!sMasterList) { michael@0: return; michael@0: } michael@0: michael@0: PerThreadData* ptd = sThreadLocalData.get(); michael@0: if (!ptd) { michael@0: // In this case the current thread is not registered with IOInterposer. michael@0: // Alternatively we could take the slow path and just lock everything if michael@0: // we're not registered. That could potentially perform poorly, though. michael@0: return; michael@0: } michael@0: sMasterList->Update(*ptd); michael@0: michael@0: // Don't try to report if there's nobody listening. michael@0: if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) { michael@0: return; michael@0: } michael@0: michael@0: ptd->CallObservers(aObservation); michael@0: } michael@0: michael@0: bool michael@0: IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) michael@0: { michael@0: return sMasterList && sMasterList->IsObservedOperation(aOp); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::Register(IOInterposeObserver::Operation aOp, michael@0: IOInterposeObserver* aObserver) michael@0: { michael@0: MOZ_ASSERT(aObserver); michael@0: if (!sMasterList || !aObserver) { michael@0: return; michael@0: } michael@0: michael@0: sMasterList->Register(aOp, aObserver); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::Unregister(IOInterposeObserver::Operation aOp, michael@0: IOInterposeObserver* aObserver) michael@0: { michael@0: if (!sMasterList) { michael@0: return; michael@0: } michael@0: michael@0: sMasterList->Unregister(aOp, aObserver); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::RegisterCurrentThread(bool aIsMainThread) michael@0: { michael@0: if (!sThreadLocalData.initialized()) { michael@0: return; michael@0: } michael@0: MOZ_ASSERT(!sThreadLocalData.get()); michael@0: PerThreadData* curThreadData = new PerThreadData(aIsMainThread); michael@0: sThreadLocalData.set(curThreadData); michael@0: } michael@0: michael@0: void michael@0: IOInterposer::UnregisterCurrentThread() michael@0: { michael@0: if (!sThreadLocalData.initialized()) { michael@0: return; michael@0: } michael@0: PerThreadData* curThreadData = sThreadLocalData.get(); michael@0: MOZ_ASSERT(curThreadData); michael@0: sThreadLocalData.set(nullptr); michael@0: delete curThreadData; michael@0: } michael@0: michael@0: void michael@0: IOInterposer::EnteringNextStage() michael@0: { michael@0: if (!sMasterList) { michael@0: return; michael@0: } michael@0: NextStageObservation observation; michael@0: Report(observation); michael@0: } michael@0: