Line data Source code
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/. */
6 :
7 : #include "nsThreadManager.h"
8 : #include "nsThread.h"
9 : #include "nsThreadUtils.h"
10 : #include "nsIClassInfoImpl.h"
11 : #include "nsTArray.h"
12 : #include "nsAutoPtr.h"
13 : #include "mozilla/AbstractThread.h"
14 : #include "mozilla/ThreadLocal.h"
15 : #ifdef MOZ_CANARY
16 : #include <fcntl.h>
17 : #include <unistd.h>
18 : #endif
19 :
20 : #include "MainThreadIdlePeriod.h"
21 :
22 : using namespace mozilla;
23 :
24 : static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
25 :
26 : bool
27 613613 : NS_IsMainThread()
28 : {
29 613613 : return sTLSIsMainThread.get();
30 : }
31 :
32 : void
33 11 : NS_SetMainThread()
34 : {
35 11 : if (!sTLSIsMainThread.init()) {
36 0 : MOZ_CRASH();
37 : }
38 11 : sTLSIsMainThread.set(true);
39 11 : MOZ_ASSERT(NS_IsMainThread());
40 11 : }
41 :
42 : typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray;
43 :
44 : //-----------------------------------------------------------------------------
45 :
46 : static void
47 1 : ReleaseObject(void* aData)
48 : {
49 1 : static_cast<nsISupports*>(aData)->Release();
50 1 : }
51 :
52 : // statically allocated instance
53 : NS_IMETHODIMP_(MozExternalRefCountType)
54 28 : nsThreadManager::AddRef()
55 : {
56 28 : return 2;
57 : }
58 : NS_IMETHODIMP_(MozExternalRefCountType)
59 15 : nsThreadManager::Release()
60 : {
61 15 : return 1;
62 : }
63 3 : NS_IMPL_CLASSINFO(nsThreadManager, nullptr,
64 : nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
65 : NS_THREADMANAGER_CID)
66 68 : NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager)
67 1 : NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
68 :
69 : //-----------------------------------------------------------------------------
70 :
71 : nsresult
72 5 : nsThreadManager::Init()
73 : {
74 : // Child processes need to initialize the thread manager before they
75 : // initialize XPCOM in order to set up the crash reporter. This leads to
76 : // situations where we get initialized twice.
77 5 : if (mInitialized) {
78 2 : return NS_OK;
79 : }
80 :
81 3 : if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) {
82 0 : return NS_ERROR_FAILURE;
83 : }
84 :
85 :
86 : #ifdef MOZ_CANARY
87 : const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
88 : const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
89 : char* env_var_flag = getenv("MOZ_KILL_CANARIES");
90 : sCanaryOutputFD =
91 : env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) :
92 : STDERR_FILENO) :
93 : 0;
94 : #endif
95 :
96 : // Setup "main" thread
97 3 : mMainThread = new nsThread(nsThread::MAIN_THREAD, 0);
98 :
99 3 : nsresult rv = mMainThread->InitCurrentThread();
100 3 : if (NS_FAILED(rv)) {
101 0 : mMainThread = nullptr;
102 0 : return rv;
103 : }
104 :
105 : {
106 6 : nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
107 3 : mMainThread->RegisterIdlePeriod(idlePeriod.forget());
108 : }
109 :
110 : // We need to keep a pointer to the current thread, so we can satisfy
111 : // GetIsMainThread calls that occur post-Shutdown.
112 3 : mMainThread->GetPRThread(&mMainPRThread);
113 :
114 : // Init AbstractThread.
115 3 : AbstractThread::InitTLS();
116 3 : AbstractThread::InitMainThread();
117 :
118 3 : mInitialized = true;
119 3 : return NS_OK;
120 : }
121 :
122 : void
123 0 : nsThreadManager::Shutdown()
124 : {
125 0 : MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
126 :
127 : // Prevent further access to the thread manager (no more new threads!)
128 : //
129 : // What happens if shutdown happens before NewThread completes?
130 : // We Shutdown() the new thread, and return error if we've started Shutdown
131 : // between when NewThread started, and when the thread finished initializing
132 : // and registering with ThreadManager.
133 : //
134 0 : mInitialized = false;
135 :
136 : // Empty the main thread event queue before we begin shutting down threads.
137 0 : NS_ProcessPendingEvents(mMainThread);
138 :
139 : // We gather the threads from the hashtable into a list, so that we avoid
140 : // holding the hashtable lock while calling nsIThread::Shutdown.
141 0 : nsThreadArray threads;
142 : {
143 0 : OffTheBooksMutexAutoLock lock(mLock);
144 0 : for (auto iter = mThreadsByPRThread.Iter(); !iter.Done(); iter.Next()) {
145 0 : RefPtr<nsThread>& thread = iter.Data();
146 0 : threads.AppendElement(WrapNotNull(thread));
147 0 : iter.Remove();
148 : }
149 : }
150 :
151 : // It's tempting to walk the list of threads here and tell them each to stop
152 : // accepting new events, but that could lead to badness if one of those
153 : // threads is stuck waiting for a response from another thread. To do it
154 : // right, we'd need some way to interrupt the threads.
155 : //
156 : // Instead, we process events on the current thread while waiting for threads
157 : // to shutdown. This means that we have to preserve a mostly functioning
158 : // world until such time as the threads exit.
159 :
160 : // Shutdown all threads that require it (join with threads that we created).
161 0 : for (uint32_t i = 0; i < threads.Length(); ++i) {
162 0 : NotNull<nsThread*> thread = threads[i];
163 0 : if (thread->ShutdownRequired()) {
164 0 : thread->Shutdown();
165 : }
166 : }
167 :
168 : // NB: It's possible that there are events in the queue that want to *start*
169 : // an asynchronous shutdown. But we have already shutdown the threads above,
170 : // so there's no need to worry about them. We only have to wait for all
171 : // in-flight asynchronous thread shutdowns to complete.
172 0 : mMainThread->WaitForAllAsynchronousShutdowns();
173 :
174 : // In case there are any more events somehow...
175 0 : NS_ProcessPendingEvents(mMainThread);
176 :
177 : // There are no more background threads at this point.
178 :
179 : // Clear the table of threads.
180 : {
181 0 : OffTheBooksMutexAutoLock lock(mLock);
182 0 : mThreadsByPRThread.Clear();
183 : }
184 :
185 : // Normally thread shutdown clears the observer for the thread, but since the
186 : // main thread is special we do it manually here after we're sure all events
187 : // have been processed.
188 0 : mMainThread->SetObserver(nullptr);
189 0 : mMainThread->ClearObservers();
190 :
191 : // Release main thread object.
192 0 : mMainThread = nullptr;
193 :
194 : // Remove the TLS entry for the main thread.
195 0 : PR_SetThreadPrivate(mCurThreadIndex, nullptr);
196 0 : }
197 :
198 : void
199 66 : nsThreadManager::RegisterCurrentThread(nsThread& aThread)
200 : {
201 66 : MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
202 :
203 132 : OffTheBooksMutexAutoLock lock(mLock);
204 :
205 66 : ++mCurrentNumberOfThreads;
206 66 : if (mCurrentNumberOfThreads > mHighestNumberOfThreads) {
207 65 : mHighestNumberOfThreads = mCurrentNumberOfThreads;
208 : }
209 :
210 66 : mThreadsByPRThread.Put(aThread.GetPRThread(), &aThread); // XXX check OOM?
211 :
212 66 : aThread.AddRef(); // for TLS entry
213 66 : PR_SetThreadPrivate(mCurThreadIndex, &aThread);
214 66 : }
215 :
216 : void
217 1 : nsThreadManager::UnregisterCurrentThread(nsThread& aThread)
218 : {
219 1 : MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
220 :
221 2 : OffTheBooksMutexAutoLock lock(mLock);
222 :
223 1 : --mCurrentNumberOfThreads;
224 1 : mThreadsByPRThread.Remove(aThread.GetPRThread());
225 :
226 1 : PR_SetThreadPrivate(mCurThreadIndex, nullptr);
227 : // Ref-count balanced via ReleaseObject
228 1 : }
229 :
230 : nsThread*
231 909 : nsThreadManager::GetCurrentThread()
232 : {
233 : // read thread local storage
234 909 : void* data = PR_GetThreadPrivate(mCurThreadIndex);
235 909 : if (data) {
236 899 : return static_cast<nsThread*>(data);
237 : }
238 :
239 10 : if (!mInitialized) {
240 3 : return nullptr;
241 : }
242 :
243 : // OK, that's fine. We'll dynamically create one :-)
244 14 : RefPtr<nsThread> thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0);
245 7 : if (!thread || NS_FAILED(thread->InitCurrentThread())) {
246 0 : return nullptr;
247 : }
248 :
249 7 : return thread.get(); // reference held in TLS
250 : }
251 :
252 : NS_IMETHODIMP
253 0 : nsThreadManager::NewThread(uint32_t aCreationFlags,
254 : uint32_t aStackSize,
255 : nsIThread** aResult)
256 : {
257 0 : return NewNamedThread(NS_LITERAL_CSTRING(""), aStackSize, aResult);
258 : }
259 :
260 : NS_IMETHODIMP
261 55 : nsThreadManager::NewNamedThread(const nsACString& aName,
262 : uint32_t aStackSize,
263 : nsIThread** aResult)
264 : {
265 : // Note: can be called from arbitrary threads
266 :
267 : // No new threads during Shutdown
268 55 : if (NS_WARN_IF(!mInitialized)) {
269 0 : return NS_ERROR_NOT_INITIALIZED;
270 : }
271 :
272 110 : RefPtr<nsThread> thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize);
273 55 : nsresult rv = thr->Init(aName); // Note: blocks until the new thread has been set up
274 55 : if (NS_FAILED(rv)) {
275 0 : return rv;
276 : }
277 :
278 : // At this point, we expect that the thread has been registered in mThreadByPRThread;
279 : // however, it is possible that it could have also been replaced by now, so
280 : // we cannot really assert that it was added. Instead, kill it if we entered
281 : // Shutdown() during/before Init()
282 :
283 55 : if (NS_WARN_IF(!mInitialized)) {
284 0 : if (thr->ShutdownRequired()) {
285 0 : thr->Shutdown(); // ok if it happens multiple times
286 : }
287 0 : return NS_ERROR_NOT_INITIALIZED;
288 : }
289 :
290 55 : thr.forget(aResult);
291 55 : return NS_OK;
292 : }
293 :
294 : NS_IMETHODIMP
295 646 : nsThreadManager::GetThreadFromPRThread(PRThread* aThread, nsIThread** aResult)
296 : {
297 : // Keep this functioning during Shutdown
298 646 : if (NS_WARN_IF(!mMainThread)) {
299 0 : return NS_ERROR_NOT_INITIALIZED;
300 : }
301 646 : if (NS_WARN_IF(!aThread)) {
302 0 : return NS_ERROR_INVALID_ARG;
303 : }
304 :
305 1292 : RefPtr<nsThread> temp;
306 : {
307 1292 : OffTheBooksMutexAutoLock lock(mLock);
308 646 : mThreadsByPRThread.Get(aThread, getter_AddRefs(temp));
309 : }
310 :
311 646 : NS_IF_ADDREF(*aResult = temp);
312 646 : return NS_OK;
313 : }
314 :
315 : NS_IMETHODIMP
316 798 : nsThreadManager::GetMainThread(nsIThread** aResult)
317 : {
318 : // Keep this functioning during Shutdown
319 798 : if (NS_WARN_IF(!mMainThread)) {
320 0 : return NS_ERROR_NOT_INITIALIZED;
321 : }
322 798 : NS_ADDREF(*aResult = mMainThread);
323 798 : return NS_OK;
324 : }
325 :
326 : NS_IMETHODIMP
327 739 : nsThreadManager::GetCurrentThread(nsIThread** aResult)
328 : {
329 : // Keep this functioning during Shutdown
330 739 : if (NS_WARN_IF(!mMainThread)) {
331 0 : return NS_ERROR_NOT_INITIALIZED;
332 : }
333 739 : *aResult = GetCurrentThread();
334 739 : if (!*aResult) {
335 0 : return NS_ERROR_OUT_OF_MEMORY;
336 : }
337 739 : NS_ADDREF(*aResult);
338 739 : return NS_OK;
339 : }
340 :
341 : NS_IMETHODIMP
342 0 : nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition)
343 : {
344 0 : nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
345 0 : nsresult rv = NS_OK;
346 :
347 0 : if (!mozilla::SpinEventLoopUntil([&]() -> bool {
348 0 : bool isDone = false;
349 0 : rv = condition->IsDone(&isDone);
350 : // JS failure should be unusual, but we need to stop and propagate
351 : // the error back to the caller.
352 0 : if (NS_FAILED(rv)) {
353 0 : return true;
354 : }
355 :
356 0 : return isDone;
357 : })) {
358 : // We stopped early for some reason, which is unexpected.
359 0 : return NS_ERROR_UNEXPECTED;
360 : }
361 :
362 : // If we exited when the condition told us to, we need to return whether
363 : // the condition encountered failure when executing.
364 0 : return rv;
365 : }
366 :
367 : NS_IMETHODIMP
368 0 : nsThreadManager::SpinEventLoopUntilEmpty()
369 : {
370 0 : nsIThread* thread = NS_GetCurrentThread();
371 :
372 0 : while (NS_HasPendingEvents(thread)) {
373 0 : (void)NS_ProcessNextEvent(thread, false);
374 : }
375 :
376 0 : return NS_OK;
377 : }
378 :
379 : uint32_t
380 0 : nsThreadManager::GetHighestNumberOfThreads()
381 : {
382 0 : OffTheBooksMutexAutoLock lock(mLock);
383 0 : return mHighestNumberOfThreads;
384 : }
385 :
386 : NS_IMETHODIMP
387 6 : nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent)
388 : {
389 : // Note: C++ callers should instead use NS_DispatchToMainThread.
390 6 : MOZ_ASSERT(NS_IsMainThread());
391 :
392 : // Keep this functioning during Shutdown
393 6 : if (NS_WARN_IF(!mMainThread)) {
394 0 : return NS_ERROR_NOT_INITIALIZED;
395 : }
396 :
397 6 : return mMainThread->DispatchFromScript(aEvent, 0);
398 : }
399 :
400 : NS_IMETHODIMP
401 1 : nsThreadManager::IdleDispatchToMainThread(nsIRunnable *aEvent, uint32_t aTimeout)
402 : {
403 : // Note: C++ callers should instead use NS_IdleDispatchToThread or
404 : // NS_IdleDispatchToCurrentThread.
405 1 : MOZ_ASSERT(NS_IsMainThread());
406 :
407 2 : nsCOMPtr<nsIRunnable> event(aEvent);
408 1 : if (aTimeout) {
409 0 : return NS_IdleDispatchToThread(event.forget(), aTimeout, mMainThread);
410 : }
411 :
412 1 : return NS_IdleDispatchToThread(event.forget(), mMainThread);
413 : }
|