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/. */
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
21 # include "NSPRInterposer.h"
22 #endif // !defined(XP_WIN)
23 #include "nsXULAppAPI.h"
24 #include "PoisonIOInterposer.h"
29 /** Find if a vector contains a specific element */
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 */
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
{
46 ~ObserverLists() = default;
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
;
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
) {
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
;
98 case mozilla::IOInterposeObserver::OpRead
:
99 observers
= &mObserverLists
->mReadObservers
;
101 case mozilla::IOInterposeObserver::OpWrite
:
102 observers
= &mObserverLists
->mWriteObservers
;
104 case mozilla::IOInterposeObserver::OpFSync
:
105 observers
= &mObserverLists
->mFSyncObservers
;
107 case mozilla::IOInterposeObserver::OpStat
:
108 observers
= &mObserverLists
->mStatObservers
;
110 case mozilla::IOInterposeObserver::OpClose
:
111 observers
= &mObserverLists
->mCloseObservers
;
113 case mozilla::IOInterposeObserver::OpNextStage
:
114 observers
= &mObserverLists
->mStageObservers
;
117 // Invalid IO operation, see documentation comment for
118 // IOInterposer::Report()
120 // Just ignore it in non-debug builds.
124 MOZ_ASSERT(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;
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.
162 : mObservedOperations(mozilla::IOInterposeObserver::OpNone
),
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
);
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
);
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()) {
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
299 return mIsEnabled
&& !!(mObservedOperations
& aOp
);
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
309 mozilla::IOInterposer::Mutex mLock
;
310 // Flags tracking which operations are being observed
311 mozilla::Atomic
<mozilla::IOInterposeObserver::Operation
,
312 mozilla::MemoryOrdering::Relaxed
>
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
{
323 NextStageObservation()
324 : mozilla::IOInterposeObserver::Observation(
325 mozilla::IOInterposeObserver::OpNextStage
, "IOInterposer", false) {
326 mStart
= mozilla::TimeStamp::Now();
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
340 IOInterposeObserver::Observation::Observation(Operation aOperation
,
341 const char* aReference
,
343 : mOperation(aOperation
),
344 mReference(aReference
),
345 mShouldReport(IOInterposer::IsObservedOperation(aOperation
) &&
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
),
359 mReference(aReference
),
360 mShouldReport(false) {}
362 const char* IOInterposeObserver::Observation::ObservedOperationString() const {
363 switch (mOperation
) {
365 return "create/open";
383 void IOInterposeObserver::Observation::Report() {
385 mEnd
= TimeStamp::Now();
386 IOInterposer::Report(*this);
390 bool IOInterposer::Init() {
391 // Don't initialize twice...
395 if (!sThreadLocalData
.init()) {
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.
417 InitNSPRIOInterposing();
422 bool IOInterposeObserver::IsMainThread() {
423 if (!sThreadLocalDataInitialized
) {
426 PerThreadData
* ptd
= sThreadLocalData
.get();
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;
443 void IOInterposer::Disable() {
447 sSourceList
->Disable();
450 void IOInterposer::Enable() {
454 sSourceList
->Enable();
457 void IOInterposer::Report(IOInterposeObserver::Observation
& aObservation
) {
458 PerThreadData
* ptd
= sThreadLocalData
.get();
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.
467 // If there is no longer a source list then we should clear the local one.
468 ptd
->ClearObserverLists();
472 sSourceList
->Update(*ptd
);
474 // Don't try to report if there's nobody listening.
475 if (!IOInterposer::IsObservedOperation(aObservation
.ObservedOperation())) {
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
) {
493 sSourceList
->Register(aOp
, aStaticObserver
);
496 void IOInterposer::Unregister(IOInterposeObserver::Operation aOp
,
497 IOInterposeObserver
* aStaticObserver
) {
502 sSourceList
->Unregister(aOp
, aStaticObserver
);
505 void IOInterposer::RegisterCurrentThread(bool aIsMainThread
) {
506 if (!sThreadLocalDataInitialized
) {
509 MOZ_ASSERT(!sThreadLocalData
.get());
510 PerThreadData
* curThreadData
= new PerThreadData(aIsMainThread
);
511 sThreadLocalData
.set(curThreadData
);
514 void IOInterposer::UnregisterCurrentThread() {
515 if (!sThreadLocalDataInitialized
) {
518 if (PerThreadData
* curThreadData
= sThreadLocalData
.get()) {
519 sThreadLocalData
.set(nullptr);
520 delete curThreadData
;
524 void IOInterposer::EnteringNextStage() {
528 NextStageObservation observation
;
532 } // namespace mozilla