summaryrefslogtreecommitdiffstats
path: root/xpcom/build/IOInterposer.h
blob: 917aa26cd4bee082ae120eb33f1ef2c76d1cb051 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/* -*- 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 mozilla_IOInterposer_h
#define mozilla_IOInterposer_h

#include "mozilla/Attributes.h"
#include "mozilla/TimeStamp.h"
#include "nsString.h"

namespace mozilla {

/**
 * Interface for I/O interposer observers. This is separate from the
 * IOInterposer because we have multiple uses for these observations.
 */
class IOInterposeObserver {
 public:
  enum Operation {
    OpNone = 0,
    OpCreateOrOpen = (1 << 0),
    OpRead = (1 << 1),
    OpWrite = (1 << 2),
    OpFSync = (1 << 3),
    OpStat = (1 << 4),
    OpClose = (1 << 5),
    OpNextStage =
        (1 << 6),  // Meta - used when leaving startup, entering shutdown
    OpWriteFSync = (OpWrite | OpFSync),
    OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose),
    OpAllWithStaging = (OpAll | OpNextStage)
  };

  /** A representation of an I/O observation  */
  class Observation {
   protected:
    /**
     * This constructor is for use by subclasses that are intended to take
     * timing measurements via RAII. The |aShouldReport| parameter may be
     * used to make the measurement and reporting conditional on the
     * satisfaction of an arbitrary predicate that was evaluated
     * in the subclass. Note that IOInterposer::IsObservedOperation() is
     * always ANDed with aShouldReport, so the subclass does not need to
     * include a call to that function explicitly.
     */
    Observation(Operation aOperation, const char* aReference,
                bool aShouldReport = true);

   public:
    /**
     * Since this constructor accepts start and end times, it does *not* take
     * its own timings, nor does it report itself.
     */
    Observation(Operation aOperation, const TimeStamp& aStart,
                const TimeStamp& aEnd, const char* aReference);

    /**
     * Operation observed, this is one of the individual Operation values.
     * Combinations of these flags are only used when registering observers.
     */
    Operation ObservedOperation() const { return mOperation; }

    /**
     * Return the observed operation as a human-readable string.
     */
    const char* ObservedOperationString() const;

    /** Time at which the I/O operation was started */
    TimeStamp Start() const { return mStart; }

    /**
     * Time at which the I/O operation ended, for asynchronous methods this is
     * the time at which the call initiating the asynchronous request returned.
     */
    TimeStamp End() const { return mEnd; }

    /**
     * Duration of the operation, for asynchronous I/O methods this is the
     * duration of the call initiating the asynchronous request.
     */
    TimeDuration Duration() const { return mEnd - mStart; }

    /**
     * IO reference, function name or name of component (sqlite) that did IO
     * this is in addition the generic operation. This attribute may be platform
     * specific, but should only take a finite number of distinct values.
     * E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc.
     * I.e. typically the platform specific function that did the IO.
     */
    const char* Reference() const { return mReference; }

    virtual const char* FileType() const { return "File"; }

    /** Request filename associated with the I/O operation, empty if unknown */
    virtual void Filename(nsAString& aString) { aString.Truncate(); }

    virtual ~Observation() = default;

   protected:
    void Report();

    Operation mOperation;
    TimeStamp mStart;
    TimeStamp mEnd;
    const char* mReference;  // Identifies the source of the Observation
    bool mShouldReport;      // Measure and report if true
  };

  /**
   * Invoked whenever an implementation of the IOInterposeObserver should
   * observe aObservation. Implement this and do your thing...
   * But do consider if it is wise to use IO functions in this method, they are
   * likely to cause recursion :)
   * At least, see PoisonIOInterposer.h and register your handle as a debug file
   * even, if you don't initialize the poison IO interposer, someone else might.
   *
   * Remark: Observations may occur on any thread.
   */
  virtual void Observe(Observation& aObservation) = 0;

  virtual ~IOInterposeObserver() = default;

 protected:
  /**
   * We don't use NS_IsMainThread() because we need to be able to determine the
   * main thread outside of XPCOM Initialization. IOInterposer observers should
   * call this function instead.
   */
  static bool IsMainThread();
};

/**
 * These functions are responsible for ensuring that events are routed to the
 * appropriate observers.
 */
namespace IOInterposer {

/**
 * This function must be called from the main-thread when no other threads are
 * running before any of the other methods on this class may be used.
 *
 * IO reports can however, safely assume that IsObservedOperation() will
 * return false until the IOInterposer is initialized.
 *
 * Remark, it's safe to call this method multiple times, so just call it when
 * you to utilize IO interposing.
 *
 * Using the IOInterposerInit class is preferred to calling this directly.
 */
bool Init();

/**
 * This function must be called from the main thread, and furthermore
 * it must be called when no other threads are executing. Effectively
 * restricting us to calling it only during shutdown.
 *
 * Callers should take care that no other consumers are subscribed to events,
 * as these events will stop when this function is called.
 *
 * In practice, we don't use this method as the IOInterposer is used for
 * late-write checks.
 */
void Clear();

/**
 * This function immediately disables IOInterposer functionality in a fast,
 * thread-safe manner. Primarily for use by the crash reporter.
 */
void Disable();

/**
 * This function re-enables IOInterposer functionality in a fast, thread-safe
 * manner.  Primarily for use by the crash reporter.
 */
void Enable();

/**
 * Report IO to registered observers.
 * Notice that the reported operation must be either OpRead, OpWrite or
 * OpFSync. You are not allowed to report an observation with OpWriteFSync or
 * OpAll, these are just auxiliary values for use with Register().
 *
 * If the IO call you're reporting does multiple things, write and fsync, you
 * can choose to call Report() twice once with write and once with FSync. You
 * may not call Report() with OpWriteFSync! The Observation::mOperation
 * attribute is meant to be generic, not perfect.
 *
 * Notice that there is no reason to report an observation with an operation
 * which is not being observed. Use IsObservedOperation() to check if the
 * operation you are about to report is being observed. This is especially
 * important if you are constructing expensive observations containing
 * filename and full-path.
 *
 * Remark: Init() must be called before any IO is reported. But
 * IsObservedOperation() will return false until Init() is called.
 */
void Report(IOInterposeObserver::Observation& aObservation);

/**
 * Return whether or not an operation is observed. Reporters should not
 * report operations that are not being observed by anybody. This mechanism
 * allows us to avoid reporting I/O when no observers are registered.
 */
bool IsObservedOperation(IOInterposeObserver::Operation aOp);

/**
 * Register IOInterposeObserver, the observer object will receive all
 * observations for the given operation aOp.
 *
 * Remarks:
 * - Init() must be called before observers are registered.
 * - The IOInterposeObserver object should be static, because it could still be
 *   used on another thread shortly after Unregister().
 */
void Register(IOInterposeObserver::Operation aOp,
              IOInterposeObserver* aStaticObserver);

/**
 * Unregister an IOInterposeObserver for a given operation
 * Remark: It is always safe to unregister for all operations, even if yoú
 * didn't register for them all.
 * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver)
 *
 * Remarks:
 * - Init() must be called before observers are registered.
 * - The IOInterposeObserver object should be static, because it could still be
 *   used on another thread shortly after this Unregister() call.
 */
void Unregister(IOInterposeObserver::Operation aOp,
                IOInterposeObserver* aStaticObserver);

/**
 * Registers the current thread with the IOInterposer. This must be done to
 * ensure that per-thread data is created in an orderly fashion.
 * We could have written this to initialize that data lazily, however this
 * could have unintended consequences if a thread that is not aware of
 * IOInterposer was implicitly registered: its per-thread data would never
 * be deleted because it would not know to unregister itself.
 *
 * @param aIsMainThread true if IOInterposer should treat the current thread
 *                      as the main thread.
 */
void RegisterCurrentThread(bool aIsMainThread = false);

/**
 * Unregisters the current thread with the IOInterposer. This is important
 * to call when a thread is shutting down because it cleans up data that
 * is stored in a TLS slot.
 */
void UnregisterCurrentThread();

/**
 * Called to inform observers that the process has transitioned out of the
 * startup stage or into the shutdown stage. Main thread only.
 */
void EnteringNextStage();

}  // namespace IOInterposer

class IOInterposerInit {
 public:
  IOInterposerInit() {
#if defined(EARLY_BETA_OR_EARLIER)
    IOInterposer::Init();
#endif
  }

  ~IOInterposerInit() {
#if defined(EARLY_BETA_OR_EARLIER)
    IOInterposer::Clear();
#endif
  }
};

class MOZ_RAII AutoIOInterposerDisable final {
 public:
  explicit AutoIOInterposerDisable() { IOInterposer::Disable(); }
  ~AutoIOInterposerDisable() { IOInterposer::Enable(); }

 private:
};

}  // namespace mozilla

#endif  // mozilla_IOInterposer_h