1 **Thread safety analysis in Gecko**
2 ===================================
4 Clang thread-safety analysis is supported in Gecko. This means
5 builds will generate warnings when static analysis detects an issue with
6 locking of mutex/monitor-protected members and data structures. Note
7 that Chrome uses the same feature. An example warning: ::
9 warning: dom/media/AudioStream.cpp:504:22 [-Wthread-safety-analysis]
10 reading variable 'mDataSource' requires holding mutex 'mMonitor'
12 If your patch causes warnings like this, you’ll need to resolve them;
13 they will be errors on checkin.
15 This analysis depends on thread-safety attributions in the source. These
16 have been added to Mozilla’s Mutex and Monitor classes and subclasses,
17 but in practice the analysis is largely dependent on additions to the
18 code being checked, in particular adding MOZ_GUARDED_BY(mutex) attributions
19 on the definitions of member variables. Like this: ::
22 bool mShutdown MOZ_GUARDED_BY(mLock);
24 For background on Clang’s thread-safety support, see `their
25 documentation <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`__.
27 Newly added Mutexes and Monitors **MUST** use thread-safety annotations,
28 and we are enabling static checks to verify this. Legacy uses of Mutexes
29 and Monitors are marked with MOZ_UNANNOTATED.
31 If you’re modifying code that has been annotated with
32 MOZ_GUARDED_BY()/MOZ_REQUIRES()/etc, you should **make sure that the annotations
33 are updated properly**; e.g. if you change the thread-usage of a member
34 variable or method you should mark it accordingly, comment, and resolve
35 any warnings. Since the warnings will be errors in autoland/m-c, you
36 won’t be able to land code with active warnings.
38 **Annotating locking and usage requirements in class definitions**
39 ------------------------------------------------------------------
41 Values that require a lock to access, or which are simply used from more
42 than one thread, should always have documentation in the definition
43 about the locking requirements and/or what threads it’s touched from: ::
45 // This array is accessed from both the direct video thread, and the graph
46 // thread. Protected by mMutex.
48 nsTArray<std::pair<ImageContainer::FrameID, VideoChunk>> mFrames
49 MOZ_GUARDED_BY(mMutex);
51 // Set on MainThread, deleted on either MainThread mainthread, used on
52 // MainThread or IO Thread in DoStopSession
53 nsCOMPtr<nsITimer> mReconnectDelayTimer MOZ_GUARDED_BY(mMutex);
55 It’s strongly recommended to group values by access pattern, but it’s
56 **critical** to make it clear what the requirements to access a value
57 are. With values protected by Mutexes and Monitors, adding a
58 MOZ_GUARDED_BY(mutex/monitor) should be sufficient, though you may want to
59 also document what threads access it, and if they read or write to it.
61 Values which have more complex access requirements (see single-writer
62 and time-based-locking below) need clear documentation where they’re
65 MutexSingleWriter mMutex;
67 // mResource should only be modified on the main thread with the lock.
68 // So it can be read without lock on the main thread or on other threads
70 RefPtr<ChannelMediaResource> mResource MOZ_GUARDED_BY(mMutex);
72 **WARNING:** thread-safety analysis is not magic; it depends on you telling
73 it the requirements around access. If you don’t mark something as
74 MOZ_GUARDED_BY() it won’t figure it out for you, and you can end up with a data
75 race. When writing multithreaded code, you should always be thinking about
76 which threads can access what and when, and document this.
78 **How to annotate different locking patterns in Gecko**
79 -------------------------------------------------------
81 Gecko uses a number of different locking patterns. They include:
84 Multiple threads may read and write the value
87 One thread does all the writing, other threads
88 read the value, but code on the writing thread also reads it
91 - **Out-of-band invariants** -
92 A value may be accessed from other threads,
93 but only after or before certain other events or in a certain state,
94 like when after a listener has been added or before a processing
95 thread has been shut down.
97 The simplest and easiest to check with static analysis is **Always
98 Lock**, and generally you should prefer this pattern. This is very
99 simple; you add MOZ_GUARDED_BY(mutex/monitor), and must own the lock to
100 access the value. This can be implemented by some combination of direct
101 Lock/AutoLock calls in the method; an assertion that the lock is already
102 held by the current thread, or annotating the method as requiring the
103 lock (MOZ_REQUIRES(mutex)) in the method definition: ::
105 // Ensures mSize is initialized, if it can be.
106 // mLock must be held when this is called, and mInput must be non-null.
107 void EnsureSizeInitialized() MOZ_REQUIRES(mLock);
109 // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
111 int64_t mSize MOZ_GUARDED_BY(mLock);
113 **Single Writer** is tricky for static analysis normally, since it
114 doesn’t know what thread an access will occur on. In general, you should
115 prefer using Always Lock in non-performance-sensitive code, especially
116 since these mutexes are almost always uncontended and therefore cheap to
119 To support this fairly common pattern in Mozilla code, we’ve added
120 MutexSingleWriter and MonitorSingleWriter subclasses. To use these, you
121 need to subclass SingleWriterLockOwner on one object (typically the
122 object containing the Mutex), implement ::OnWritingThread(), and pass
123 the object to the constructor for MutexSingleWriter. In code that
124 accesses the guarded value from the writing thread, you need to add
125 mMutex.AssertIsOnWritingThread(), which both does a debug-only runtime
126 assertion by calling OnWritingThread(), and also asserts to the static
127 analyzer that the lock is held (which it isn’t).
129 There is one case this causes problems with: when a method needs to
130 access the value (without the lock), and then decides to write to the
131 value from the same method, taking the lock. To the static analyzer,
132 this looks like a double-lock. Either you will need to add
133 MOZ_NO_THREAD_SAFETY_ANALYSIS to the method, move the write into another
134 method you call, or locally disable the warning with
135 MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY. We’re discussing with
136 the clang static analysis developers how to better handle this.
138 Note also that this provides no checking that the lock is taken to write
141 MutexSingleWriter mMutex;
142 // mResource should only be modified on the main thread with the lock.
143 // So it can be read without lock on the main thread or on other threads
145 RefPtr<ChannelMediaResource> mResource MOZ_GUARDED_BY(mMutex);
147 nsresult ChannelMediaResource::Listener::OnStartRequest(nsIRequest *aRequest) {
148 mMutex.AssertOnWritingThread();
150 // Read from the only writing thread; no lock needed
154 return mResource->OnStartRequest(aRequest, mOffset);
157 If you need to assert you’re on the writing thread, then later take a
158 lock to modify a value, it will cause a warning: ”acquiring mutex
159 'mMutex' that is already held”. You can resolve this by turning off
160 thread-safety analysis for the lock: ::
162 mMutex.AssertOnWritingThread();
165 MOZ_PUSH_IGNORE_THREAD_SAFETY
166 MutexSingleWriterAutoLock lock(mMutex);
167 MOZ_POP_THREAD_SAFETY
169 **Out-of-band Invariants** is used in a number of places (and may be
170 combined with either of the above patterns). It's using other knowledge
171 about the execution pattern of the code to assert that it's safe to avoid
172 taking certain locks. A primary example is when a value can
173 only be accessed from a single thread for part of its lifetime (this can
174 also be referred to as "time-based locking").
176 Note that thread-safety analysis always ignores constructors and destructors
177 (which shouldn’t have races with other threads barring really odd usages).
178 Since only a single thread can access during those time periods, locking is
179 not required there. However, if a method is called from a constructor,
180 that method may generate warnings since the compiler doesn't know if it
181 might be called from elsewhere: ::
187 mBar = true; // Ok since we're in the constructor, no warning
190 void Init() { // we're only called from the constructor
191 // This causes a thread-safety warning, since the compiler
192 // can't prove that Init() is only called from the constructor
197 uint32_t mBar MOZ_GUARDED_BY(mMutex);
198 uint32_t mQuerty MOZ_GUARDED_BY(mMutex);
201 Another example might be a value that’s used from other threads, but only
202 if an observer has been installed. Thus code that always runs before the
203 observer is installed, or after it’s removed, does not need to lock.
205 These patterns are impossible to statically check in most cases. If all
206 the periods where it’s accessed from one thread only are on the same
207 thread, you could use the Single Writer pattern support to cover this
208 case. You would add AssertIsOnWritingThread() calls to methods that meet
209 the criteria that only a single thread can access the value (but only if
210 that holds). Unlike regular uses of SingleWriter, however, there’s no way
211 to check if you added such an assertion to code that runs on the “right”
212 thread, but during a period where another thread might modify it.
214 For this reason, we **strongly** suggest that you convert cases of
215 Out-of-band-invariants/Time-based-locking to Always Lock if you’re
216 refactoring the code or making major modifications. This is far less prone
217 to error, and also to future changes breaking the assumptions about other
218 threads accessing the value. In all but a few cases where code is on a very
219 ‘hot’ path, this will have no impact on performance - taking an uncontended
222 To quiet warnings where these patterns are in use, you'll need to either
223 add locks (preferred), or suppress the warnings with MOZ_NO_THREAD_SAFETY_ANALYSIS or
224 MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY.
226 **This pattern especially needs good documentation in the code as to what
227 threads will access what members under what conditions!**::
229 // Can't be accessed by multiple threads yet
230 nsresult nsAsyncStreamCopier::InitInternal(nsIInputStream* source,
231 nsIOutputStream* sink,
232 nsIEventTarget* target,
236 MOZ_NO_THREAD_SAFETY_ANALYSIS {
240 // We can't be accessed by another thread because this hasn't been
241 // added to the public list yet
242 MOZ_PUSH_IGNORE_THREAD_SAFETY
243 mRestrictedPortList.AppendElement(gBadPortList[i]);
244 MOZ_POP_THREAD_SAFETY
248 // This is called on entries in another entry's mCallback array, under the lock
249 // of that other entry. No other threads can access this entry at this time.
250 bool CacheEntry::Callback::DeferDoom(bool* aDoom) const {
252 **Known limitations**
253 ---------------------
255 **Static analysis can’t handle all reasonable patterns.** In particular,
256 per their documentation, it can’t handle conditional locks, like: ::
258 if (OnMainThread()) {
262 You should resolve this either via MOZ_NO_THREAD_SAFETY_ANALYSIS on the
263 method, or MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY.
265 **Sometimes the analyzer can’t figure out that two objects are both the
266 same Mutex**, and it will warn you. You may be able to resolve this by
267 making sure you’re using the same pattern to access the mutex: ::
269 mChan->mMonitor->AssertCurrentThreadOwns();
272 - MonitorAutoUnlock guard(*monitor);
273 + MonitorAutoUnlock guard(*(mChan->mMonitor.get())); // avoids mutex warning
274 ok = node->SendUserMessage(port, std::move(aMessage));
277 **Maybe<MutexAutoLock>** doesn’t tell the static analyzer when the mutex
278 is owned or freed; follow locking via the MayBe<> by
279 **mutex->AssertCurrentThreadOwns();** (and ditto for Monitors): ::
281 Maybe<MonitorAutoLock> lock(std::in_place, *mMonitor);
282 mMonitor->AssertCurrentThreadOwns(); // for threadsafety analysis
284 If you reset() the Maybe<>, you may need to surround it with
285 MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros: ::
287 MOZ_PUSH_IGNORE_THREAD_SAFETY
289 MOZ_POP_THREAD_SAFETY
291 **Passing a protected value by-reference** sometimes will confuse the
292 analyzer. Use MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros to
295 **Classes which need thread-safety annotations**
296 ------------------------------------------------
314 - Anything that hides an internal Mutex/etc and presents a Mutex-like
320 Some code passes **Proof-of-Lock** AutoLock parameters, as a poor form of
321 static analysis. While it’s hard to make mistakes if you pass an AutoLock
322 reference, it is possible to pass a lock to the wrong Mutex/Monitor.
324 Proof-of-lock is basically redundant to MOZ_REQUIRES() and obsolete, and
325 depends on the optimizer to remove it, and per above it can be misused,
326 with effort. With MOZ_REQUIRES(), any proof-of-lock parameters can be removed,
327 though you don't have to do so immediately.
329 In any method taking an aProofOfLock parameter, add a MOZ_REQUIRES(mutex) to
330 the definition (and optionally remove the proof-of-lock), or add a
331 mMutex.AssertCurrentThreadOwns() to the method: ::
333 nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,
334 - nsIEventTarget* aSyncLoopTarget,
335 - const MutexAutoLock& aProofOfLock);
336 + nsIEventTarget* aSyncLoopTarget) MOZ_REQUIRES(mMutex);
338 or (if for some reason it's hard to specify the mutex in the header)::
340 nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,
341 - nsIEventTarget* aSyncLoopTarget,
342 - const MutexAutoLock& aProofOfLock);
343 + nsIEventTarget* aSyncLoopTarget) {
344 + mMutex.AssertCurrentThreadOwns();
346 In addition to MOZ_GUARDED_BY() there’s also MOZ_PT_GUARDED_BY(), which says
347 that the pointer isn’t guarded, but the data pointed to by the pointer
350 Classes that expose a Mutex-like interface can be annotated like Mutex;
351 see some of the examples in the tree that use MOZ_CAPABILITY and
352 MOZ_ACQUIRE()/MOZ_RELEASE().
354 Shared locks are supported, though we don’t use them much. See RWLock.