Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / build / IOInterposer.cpp
blob0420965b9118533f014f9700640f13f7029d5e2d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <algorithm>
8 #include <vector>
10 #include "IOInterposer.h"
12 #include "IOInterposerPrivate.h"
13 #include "MainThreadIOLogger.h"
14 #include "mozilla/Atomics.h"
15 #include "mozilla/Mutex.h"
16 #include "mozilla/RefPtr.h"
17 #include "mozilla/StaticPtr.h"
18 #include "mozilla/ThreadLocal.h"
19 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
20 #if !defined(XP_WIN)
21 # include "NSPRInterposer.h"
22 #endif // !defined(XP_WIN)
23 #include "nsXULAppAPI.h"
24 #include "PoisonIOInterposer.h"
25 #include "prenv.h"
27 namespace {
29 /** Find if a vector contains a specific element */
30 template <class T>
31 bool VectorContains(const std::vector<T>& aVector, const T& aElement) {
32 return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end();
35 /** Remove element from a vector */
36 template <class T>
37 void VectorRemove(std::vector<T>& aVector, const T& aElement) {
38 typename std::vector<T>::iterator newEnd =
39 std::remove(aVector.begin(), aVector.end(), aElement);
40 aVector.erase(newEnd, aVector.end());
43 /** Lists of Observers */
44 struct ObserverLists {
45 private:
46 ~ObserverLists() = default;
48 public:
49 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists)
51 ObserverLists() = default;
53 ObserverLists(ObserverLists const& aOther)
54 : mCreateObservers(aOther.mCreateObservers),
55 mReadObservers(aOther.mReadObservers),
56 mWriteObservers(aOther.mWriteObservers),
57 mFSyncObservers(aOther.mFSyncObservers),
58 mStatObservers(aOther.mStatObservers),
59 mCloseObservers(aOther.mCloseObservers),
60 mStageObservers(aOther.mStageObservers) {}
61 // Lists of observers for I/O events.
62 // These are implemented as vectors since they are allowed to survive gecko,
63 // without reporting leaks. This is necessary for the IOInterposer to be used
64 // for late-write checks.
65 std::vector<mozilla::IOInterposeObserver*> mCreateObservers;
66 std::vector<mozilla::IOInterposeObserver*> mReadObservers;
67 std::vector<mozilla::IOInterposeObserver*> mWriteObservers;
68 std::vector<mozilla::IOInterposeObserver*> mFSyncObservers;
69 std::vector<mozilla::IOInterposeObserver*> mStatObservers;
70 std::vector<mozilla::IOInterposeObserver*> mCloseObservers;
71 std::vector<mozilla::IOInterposeObserver*> mStageObservers;
74 class PerThreadData {
75 public:
76 explicit PerThreadData(bool aIsMainThread = false)
77 : mIsMainThread(aIsMainThread),
78 mIsHandlingObservation(false),
79 mCurrentGeneration(0) {
80 MOZ_COUNT_CTOR(PerThreadData);
83 MOZ_COUNTED_DTOR(PerThreadData)
85 void CallObservers(mozilla::IOInterposeObserver::Observation& aObservation) {
86 // Prevent recursive reporting.
87 if (mIsHandlingObservation) {
88 return;
91 mIsHandlingObservation = true;
92 // Decide which list of observers to inform
93 const std::vector<mozilla::IOInterposeObserver*>* observers = nullptr;
94 switch (aObservation.ObservedOperation()) {
95 case mozilla::IOInterposeObserver::OpCreateOrOpen:
96 observers = &mObserverLists->mCreateObservers;
97 break;
98 case mozilla::IOInterposeObserver::OpRead:
99 observers = &mObserverLists->mReadObservers;
100 break;
101 case mozilla::IOInterposeObserver::OpWrite:
102 observers = &mObserverLists->mWriteObservers;
103 break;
104 case mozilla::IOInterposeObserver::OpFSync:
105 observers = &mObserverLists->mFSyncObservers;
106 break;
107 case mozilla::IOInterposeObserver::OpStat:
108 observers = &mObserverLists->mStatObservers;
109 break;
110 case mozilla::IOInterposeObserver::OpClose:
111 observers = &mObserverLists->mCloseObservers;
112 break;
113 case mozilla::IOInterposeObserver::OpNextStage:
114 observers = &mObserverLists->mStageObservers;
115 break;
116 default: {
117 // Invalid IO operation, see documentation comment for
118 // IOInterposer::Report()
119 MOZ_ASSERT(false);
120 // Just ignore it in non-debug builds.
121 return;
124 MOZ_ASSERT(observers);
126 // Inform observers
127 for (auto i = observers->begin(), e = observers->end(); i != e; ++i) {
128 (*i)->Observe(aObservation);
130 mIsHandlingObservation = false;
133 inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; }
135 inline bool IsMainThread() const { return mIsMainThread; }
137 inline void SetObserverLists(uint32_t aNewGeneration,
138 RefPtr<const ObserverLists>& aNewLists) {
139 mCurrentGeneration = aNewGeneration;
140 mObserverLists = aNewLists;
143 inline void ClearObserverLists() {
144 if (mObserverLists) {
145 mCurrentGeneration = 0;
146 mObserverLists = nullptr;
150 private:
151 bool mIsMainThread;
152 bool mIsHandlingObservation;
153 uint32_t mCurrentGeneration;
154 RefPtr<const ObserverLists> mObserverLists;
157 // Thread-safe list of observers, from which `PerThreadData` sources its own
158 // local list when needed.
159 class SourceList {
160 public:
161 SourceList()
162 : mObservedOperations(mozilla::IOInterposeObserver::OpNone),
163 mIsEnabled(true) {
164 MOZ_COUNT_CTOR(SourceList);
167 MOZ_COUNTED_DTOR(SourceList)
169 inline void Disable() { mIsEnabled = false; }
170 inline void Enable() { mIsEnabled = true; }
172 void Register(mozilla::IOInterposeObserver::Operation aOp,
173 mozilla::IOInterposeObserver* aStaticObserver) {
174 mozilla::IOInterposer::AutoLock lock(mLock);
176 ObserverLists* newLists = nullptr;
177 if (mObserverLists) {
178 newLists = new ObserverLists(*mObserverLists);
179 } else {
180 newLists = new ObserverLists();
182 // You can register to observe multiple types of observations
183 // but you'll never be registered twice for the same observations.
184 if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen &&
185 !VectorContains(newLists->mCreateObservers, aStaticObserver)) {
186 newLists->mCreateObservers.push_back(aStaticObserver);
188 if (aOp & mozilla::IOInterposeObserver::OpRead &&
189 !VectorContains(newLists->mReadObservers, aStaticObserver)) {
190 newLists->mReadObservers.push_back(aStaticObserver);
192 if (aOp & mozilla::IOInterposeObserver::OpWrite &&
193 !VectorContains(newLists->mWriteObservers, aStaticObserver)) {
194 newLists->mWriteObservers.push_back(aStaticObserver);
196 if (aOp & mozilla::IOInterposeObserver::OpFSync &&
197 !VectorContains(newLists->mFSyncObservers, aStaticObserver)) {
198 newLists->mFSyncObservers.push_back(aStaticObserver);
200 if (aOp & mozilla::IOInterposeObserver::OpStat &&
201 !VectorContains(newLists->mStatObservers, aStaticObserver)) {
202 newLists->mStatObservers.push_back(aStaticObserver);
204 if (aOp & mozilla::IOInterposeObserver::OpClose &&
205 !VectorContains(newLists->mCloseObservers, aStaticObserver)) {
206 newLists->mCloseObservers.push_back(aStaticObserver);
208 if (aOp & mozilla::IOInterposeObserver::OpNextStage &&
209 !VectorContains(newLists->mStageObservers, aStaticObserver)) {
210 newLists->mStageObservers.push_back(aStaticObserver);
212 mObserverLists = newLists;
213 mObservedOperations =
214 (mozilla::IOInterposeObserver::Operation)(mObservedOperations | aOp);
216 mCurrentGeneration++;
219 void Unregister(mozilla::IOInterposeObserver::Operation aOp,
220 mozilla::IOInterposeObserver* aStaticObserver) {
221 mozilla::IOInterposer::AutoLock lock(mLock);
223 ObserverLists* newLists = nullptr;
224 if (mObserverLists) {
225 newLists = new ObserverLists(*mObserverLists);
226 } else {
227 newLists = new ObserverLists();
230 if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen) {
231 VectorRemove(newLists->mCreateObservers, aStaticObserver);
232 if (newLists->mCreateObservers.empty()) {
233 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
234 mObservedOperations &
235 ~mozilla::IOInterposeObserver::OpCreateOrOpen);
238 if (aOp & mozilla::IOInterposeObserver::OpRead) {
239 VectorRemove(newLists->mReadObservers, aStaticObserver);
240 if (newLists->mReadObservers.empty()) {
241 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
242 mObservedOperations & ~mozilla::IOInterposeObserver::OpRead);
245 if (aOp & mozilla::IOInterposeObserver::OpWrite) {
246 VectorRemove(newLists->mWriteObservers, aStaticObserver);
247 if (newLists->mWriteObservers.empty()) {
248 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
249 mObservedOperations & ~mozilla::IOInterposeObserver::OpWrite);
252 if (aOp & mozilla::IOInterposeObserver::OpFSync) {
253 VectorRemove(newLists->mFSyncObservers, aStaticObserver);
254 if (newLists->mFSyncObservers.empty()) {
255 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
256 mObservedOperations & ~mozilla::IOInterposeObserver::OpFSync);
259 if (aOp & mozilla::IOInterposeObserver::OpStat) {
260 VectorRemove(newLists->mStatObservers, aStaticObserver);
261 if (newLists->mStatObservers.empty()) {
262 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
263 mObservedOperations & ~mozilla::IOInterposeObserver::OpStat);
266 if (aOp & mozilla::IOInterposeObserver::OpClose) {
267 VectorRemove(newLists->mCloseObservers, aStaticObserver);
268 if (newLists->mCloseObservers.empty()) {
269 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
270 mObservedOperations & ~mozilla::IOInterposeObserver::OpClose);
273 if (aOp & mozilla::IOInterposeObserver::OpNextStage) {
274 VectorRemove(newLists->mStageObservers, aStaticObserver);
275 if (newLists->mStageObservers.empty()) {
276 mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
277 mObservedOperations & ~mozilla::IOInterposeObserver::OpNextStage);
280 mObserverLists = newLists;
281 mCurrentGeneration++;
284 void Update(PerThreadData& aPtd) {
285 if (mCurrentGeneration == aPtd.GetCurrentGeneration()) {
286 return;
288 // If the generation counts don't match then we need to update the current
289 // thread's observer list with the new source list.
290 mozilla::IOInterposer::AutoLock lock(mLock);
291 aPtd.SetObserverLists(mCurrentGeneration, mObserverLists);
294 inline bool IsObservedOperation(mozilla::IOInterposeObserver::Operation aOp) {
295 // This does not occur inside of a lock, so this makes no guarantees that
296 // the observers are in sync with this. That is acceptable; it is not a
297 // problem if we occasionally report more or less IO than is actually
298 // occurring.
299 return mIsEnabled && !!(mObservedOperations & aOp);
302 private:
303 RefPtr<const ObserverLists> mObserverLists MOZ_GUARDED_BY(mLock);
304 // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked
305 // (We want to monitor IO during shutdown). Furthermore, as we may have to
306 // unregister observers during shutdown an OffTheBooksMutex is not an option
307 // either, as its base calls into sDeadlockDetector which may be nullptr
308 // during shutdown.
309 mozilla::IOInterposer::Mutex mLock;
310 // Flags tracking which operations are being observed
311 mozilla::Atomic<mozilla::IOInterposeObserver::Operation,
312 mozilla::MemoryOrdering::Relaxed>
313 mObservedOperations;
314 // Used for quickly disabling everything by IOInterposer::Disable()
315 mozilla::Atomic<bool> mIsEnabled;
316 // Used to inform threads that the source observer list has changed
317 mozilla::Atomic<uint32_t> mCurrentGeneration;
320 // Special observation used by IOInterposer::EnteringNextStage()
321 class NextStageObservation : public mozilla::IOInterposeObserver::Observation {
322 public:
323 NextStageObservation()
324 : mozilla::IOInterposeObserver::Observation(
325 mozilla::IOInterposeObserver::OpNextStage, "IOInterposer", false) {
326 mStart = mozilla::TimeStamp::Now();
327 mEnd = mStart;
331 // List of observers registered
332 static mozilla::StaticAutoPtr<SourceList> sSourceList;
333 static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData;
334 static bool sThreadLocalDataInitialized;
336 } // anonymous namespace
338 namespace mozilla {
340 IOInterposeObserver::Observation::Observation(Operation aOperation,
341 const char* aReference,
342 bool aShouldReport)
343 : mOperation(aOperation),
344 mReference(aReference),
345 mShouldReport(IOInterposer::IsObservedOperation(aOperation) &&
346 aShouldReport) {
347 if (mShouldReport) {
348 mStart = TimeStamp::Now();
352 IOInterposeObserver::Observation::Observation(Operation aOperation,
353 const TimeStamp& aStart,
354 const TimeStamp& aEnd,
355 const char* aReference)
356 : mOperation(aOperation),
357 mStart(aStart),
358 mEnd(aEnd),
359 mReference(aReference),
360 mShouldReport(false) {}
362 const char* IOInterposeObserver::Observation::ObservedOperationString() const {
363 switch (mOperation) {
364 case OpCreateOrOpen:
365 return "create/open";
366 case OpRead:
367 return "read";
368 case OpWrite:
369 return "write";
370 case OpFSync:
371 return "fsync";
372 case OpStat:
373 return "stat";
374 case OpClose:
375 return "close";
376 case OpNextStage:
377 return "NextStage";
378 default:
379 return "unknown";
383 void IOInterposeObserver::Observation::Report() {
384 if (mShouldReport) {
385 mEnd = TimeStamp::Now();
386 IOInterposer::Report(*this);
390 bool IOInterposer::Init() {
391 // Don't initialize twice...
392 if (sSourceList) {
393 return true;
395 if (!sThreadLocalData.init()) {
396 return false;
398 sThreadLocalDataInitialized = true;
399 bool isMainThread = true;
400 RegisterCurrentThread(isMainThread);
401 sSourceList = new SourceList();
403 MainThreadIOLogger::Init();
405 // Now we initialize the various interposers depending on platform
407 // Under certain conditions it may be unsafe to initialize PoisonIOInterposer,
408 // such as when a background thread is already running. We set this variable
409 // elsewhere when such a condition applies.
410 if (!PR_GetEnv("MOZ_DISABLE_POISON_IO_INTERPOSER")) {
411 InitPoisonIOInterposer();
414 // We don't hook NSPR on Windows because PoisonIOInterposer captures a
415 // superset of the former's events.
416 #if !defined(XP_WIN)
417 InitNSPRIOInterposing();
418 #endif
419 return true;
422 bool IOInterposeObserver::IsMainThread() {
423 if (!sThreadLocalDataInitialized) {
424 return false;
426 PerThreadData* ptd = sThreadLocalData.get();
427 if (!ptd) {
428 return false;
430 return ptd->IsMainThread();
433 void IOInterposer::Clear() {
434 /* Clear() is a no-op on release builds so that we may continue to trap I/O
435 until process termination. In leak-checking builds, we need to shut down
436 IOInterposer so that all references are properly released. */
437 #ifdef NS_FREE_PERMANENT_DATA
438 UnregisterCurrentThread();
439 sSourceList = nullptr;
440 #endif
443 void IOInterposer::Disable() {
444 if (!sSourceList) {
445 return;
447 sSourceList->Disable();
450 void IOInterposer::Enable() {
451 if (!sSourceList) {
452 return;
454 sSourceList->Enable();
457 void IOInterposer::Report(IOInterposeObserver::Observation& aObservation) {
458 PerThreadData* ptd = sThreadLocalData.get();
459 if (!ptd) {
460 // In this case the current thread is not registered with IOInterposer.
461 // Alternatively we could take the slow path and just lock everything if
462 // we're not registered. That could potentially perform poorly, though.
463 return;
466 if (!sSourceList) {
467 // If there is no longer a source list then we should clear the local one.
468 ptd->ClearObserverLists();
469 return;
472 sSourceList->Update(*ptd);
474 // Don't try to report if there's nobody listening.
475 if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) {
476 return;
479 ptd->CallObservers(aObservation);
482 bool IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) {
483 return sSourceList && sSourceList->IsObservedOperation(aOp);
486 void IOInterposer::Register(IOInterposeObserver::Operation aOp,
487 IOInterposeObserver* aStaticObserver) {
488 MOZ_ASSERT(aStaticObserver);
489 if (!sSourceList || !aStaticObserver) {
490 return;
493 sSourceList->Register(aOp, aStaticObserver);
496 void IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
497 IOInterposeObserver* aStaticObserver) {
498 if (!sSourceList) {
499 return;
502 sSourceList->Unregister(aOp, aStaticObserver);
505 void IOInterposer::RegisterCurrentThread(bool aIsMainThread) {
506 if (!sThreadLocalDataInitialized) {
507 return;
509 MOZ_ASSERT(!sThreadLocalData.get());
510 PerThreadData* curThreadData = new PerThreadData(aIsMainThread);
511 sThreadLocalData.set(curThreadData);
514 void IOInterposer::UnregisterCurrentThread() {
515 if (!sThreadLocalDataInitialized) {
516 return;
518 if (PerThreadData* curThreadData = sThreadLocalData.get()) {
519 sThreadLocalData.set(nullptr);
520 delete curThreadData;
524 void IOInterposer::EnteringNextStage() {
525 if (!sSourceList) {
526 return;
528 NextStageObservation observation;
529 Report(observation);
532 } // namespace mozilla