|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
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 #ifdef MOZ_CANARY |
|
14 #include <fcntl.h> |
|
15 #include <unistd.h> |
|
16 #endif |
|
17 |
|
18 using namespace mozilla; |
|
19 |
|
20 #ifdef XP_WIN |
|
21 #include <windows.h> |
|
22 DWORD gTLSThreadIDIndex = TlsAlloc(); |
|
23 #elif defined(NS_TLS) |
|
24 NS_TLS mozilla::threads::ID gTLSThreadID = mozilla::threads::Generic; |
|
25 #endif |
|
26 |
|
27 typedef nsTArray< nsRefPtr<nsThread> > nsThreadArray; |
|
28 |
|
29 //----------------------------------------------------------------------------- |
|
30 |
|
31 static void |
|
32 ReleaseObject(void *data) |
|
33 { |
|
34 static_cast<nsISupports *>(data)->Release(); |
|
35 } |
|
36 |
|
37 static PLDHashOperator |
|
38 AppendAndRemoveThread(PRThread *key, nsRefPtr<nsThread> &thread, void *arg) |
|
39 { |
|
40 nsThreadArray *threads = static_cast<nsThreadArray *>(arg); |
|
41 threads->AppendElement(thread); |
|
42 return PL_DHASH_REMOVE; |
|
43 } |
|
44 |
|
45 // statically allocated instance |
|
46 NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::AddRef() { return 2; } |
|
47 NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::Release() { return 1; } |
|
48 NS_IMPL_CLASSINFO(nsThreadManager, nullptr, |
|
49 nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, |
|
50 NS_THREADMANAGER_CID) |
|
51 NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) |
|
52 NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) |
|
53 |
|
54 //----------------------------------------------------------------------------- |
|
55 |
|
56 nsresult |
|
57 nsThreadManager::Init() |
|
58 { |
|
59 // Child processes need to initialize the thread manager before they |
|
60 // initialize XPCOM in order to set up the crash reporter. This leads to |
|
61 // situations where we get initialized twice. |
|
62 if (mInitialized) |
|
63 return NS_OK; |
|
64 |
|
65 if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) |
|
66 return NS_ERROR_FAILURE; |
|
67 |
|
68 mLock = new Mutex("nsThreadManager.mLock"); |
|
69 |
|
70 #ifdef MOZ_CANARY |
|
71 const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; |
|
72 const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; |
|
73 char* env_var_flag = getenv("MOZ_KILL_CANARIES"); |
|
74 sCanaryOutputFD = env_var_flag ? (env_var_flag[0] ? |
|
75 open(env_var_flag, flags, mode) : |
|
76 STDERR_FILENO) : 0; |
|
77 #endif |
|
78 |
|
79 // Setup "main" thread |
|
80 mMainThread = new nsThread(nsThread::MAIN_THREAD, 0); |
|
81 |
|
82 nsresult rv = mMainThread->InitCurrentThread(); |
|
83 if (NS_FAILED(rv)) { |
|
84 mMainThread = nullptr; |
|
85 return rv; |
|
86 } |
|
87 |
|
88 // We need to keep a pointer to the current thread, so we can satisfy |
|
89 // GetIsMainThread calls that occur post-Shutdown. |
|
90 mMainThread->GetPRThread(&mMainPRThread); |
|
91 |
|
92 #ifdef XP_WIN |
|
93 TlsSetValue(gTLSThreadIDIndex, (void*) mozilla::threads::Main); |
|
94 #elif defined(NS_TLS) |
|
95 gTLSThreadID = mozilla::threads::Main; |
|
96 #endif |
|
97 |
|
98 mInitialized = true; |
|
99 return NS_OK; |
|
100 } |
|
101 |
|
102 void |
|
103 nsThreadManager::Shutdown() |
|
104 { |
|
105 MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); |
|
106 |
|
107 // Prevent further access to the thread manager (no more new threads!) |
|
108 // |
|
109 // XXX What happens if shutdown happens before NewThread completes? |
|
110 // Fortunately, NewThread is only called on the main thread for now. |
|
111 // |
|
112 mInitialized = false; |
|
113 |
|
114 // Empty the main thread event queue before we begin shutting down threads. |
|
115 NS_ProcessPendingEvents(mMainThread); |
|
116 |
|
117 // We gather the threads from the hashtable into a list, so that we avoid |
|
118 // holding the hashtable lock while calling nsIThread::Shutdown. |
|
119 nsThreadArray threads; |
|
120 { |
|
121 MutexAutoLock lock(*mLock); |
|
122 mThreadsByPRThread.Enumerate(AppendAndRemoveThread, &threads); |
|
123 } |
|
124 |
|
125 // It's tempting to walk the list of threads here and tell them each to stop |
|
126 // accepting new events, but that could lead to badness if one of those |
|
127 // threads is stuck waiting for a response from another thread. To do it |
|
128 // right, we'd need some way to interrupt the threads. |
|
129 // |
|
130 // Instead, we process events on the current thread while waiting for threads |
|
131 // to shutdown. This means that we have to preserve a mostly functioning |
|
132 // world until such time as the threads exit. |
|
133 |
|
134 // Shutdown all threads that require it (join with threads that we created). |
|
135 for (uint32_t i = 0; i < threads.Length(); ++i) { |
|
136 nsThread *thread = threads[i]; |
|
137 if (thread->ShutdownRequired()) |
|
138 thread->Shutdown(); |
|
139 } |
|
140 |
|
141 // In case there are any more events somehow... |
|
142 NS_ProcessPendingEvents(mMainThread); |
|
143 |
|
144 // There are no more background threads at this point. |
|
145 |
|
146 // Clear the table of threads. |
|
147 { |
|
148 MutexAutoLock lock(*mLock); |
|
149 mThreadsByPRThread.Clear(); |
|
150 } |
|
151 |
|
152 // Normally thread shutdown clears the observer for the thread, but since the |
|
153 // main thread is special we do it manually here after we're sure all events |
|
154 // have been processed. |
|
155 mMainThread->SetObserver(nullptr); |
|
156 mMainThread->ClearObservers(); |
|
157 |
|
158 // Release main thread object. |
|
159 mMainThread = nullptr; |
|
160 mLock = nullptr; |
|
161 |
|
162 // Remove the TLS entry for the main thread. |
|
163 PR_SetThreadPrivate(mCurThreadIndex, nullptr); |
|
164 } |
|
165 |
|
166 void |
|
167 nsThreadManager::RegisterCurrentThread(nsThread *thread) |
|
168 { |
|
169 MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); |
|
170 |
|
171 MutexAutoLock lock(*mLock); |
|
172 |
|
173 ++mCurrentNumberOfThreads; |
|
174 if (mCurrentNumberOfThreads > mHighestNumberOfThreads) { |
|
175 mHighestNumberOfThreads = mCurrentNumberOfThreads; |
|
176 } |
|
177 |
|
178 mThreadsByPRThread.Put(thread->GetPRThread(), thread); // XXX check OOM? |
|
179 |
|
180 NS_ADDREF(thread); // for TLS entry |
|
181 PR_SetThreadPrivate(mCurThreadIndex, thread); |
|
182 } |
|
183 |
|
184 void |
|
185 nsThreadManager::UnregisterCurrentThread(nsThread *thread) |
|
186 { |
|
187 MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); |
|
188 |
|
189 MutexAutoLock lock(*mLock); |
|
190 |
|
191 --mCurrentNumberOfThreads; |
|
192 mThreadsByPRThread.Remove(thread->GetPRThread()); |
|
193 |
|
194 PR_SetThreadPrivate(mCurThreadIndex, nullptr); |
|
195 // Ref-count balanced via ReleaseObject |
|
196 } |
|
197 |
|
198 nsThread * |
|
199 nsThreadManager::GetCurrentThread() |
|
200 { |
|
201 // read thread local storage |
|
202 void *data = PR_GetThreadPrivate(mCurThreadIndex); |
|
203 if (data) |
|
204 return static_cast<nsThread *>(data); |
|
205 |
|
206 if (!mInitialized) { |
|
207 return nullptr; |
|
208 } |
|
209 |
|
210 // OK, that's fine. We'll dynamically create one :-) |
|
211 nsRefPtr<nsThread> thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0); |
|
212 if (!thread || NS_FAILED(thread->InitCurrentThread())) |
|
213 return nullptr; |
|
214 |
|
215 return thread.get(); // reference held in TLS |
|
216 } |
|
217 |
|
218 NS_IMETHODIMP |
|
219 nsThreadManager::NewThread(uint32_t creationFlags, |
|
220 uint32_t stackSize, |
|
221 nsIThread **result) |
|
222 { |
|
223 // No new threads during Shutdown |
|
224 if (NS_WARN_IF(!mInitialized)) |
|
225 return NS_ERROR_NOT_INITIALIZED; |
|
226 |
|
227 nsThread *thr = new nsThread(nsThread::NOT_MAIN_THREAD, stackSize); |
|
228 if (!thr) |
|
229 return NS_ERROR_OUT_OF_MEMORY; |
|
230 NS_ADDREF(thr); |
|
231 |
|
232 nsresult rv = thr->Init(); |
|
233 if (NS_FAILED(rv)) { |
|
234 NS_RELEASE(thr); |
|
235 return rv; |
|
236 } |
|
237 |
|
238 // At this point, we expect that the thread has been registered in mThread; |
|
239 // however, it is possible that it could have also been replaced by now, so |
|
240 // we cannot really assert that it was added. |
|
241 |
|
242 *result = thr; |
|
243 return NS_OK; |
|
244 } |
|
245 |
|
246 NS_IMETHODIMP |
|
247 nsThreadManager::GetThreadFromPRThread(PRThread *thread, nsIThread **result) |
|
248 { |
|
249 // Keep this functioning during Shutdown |
|
250 if (NS_WARN_IF(!mMainThread)) |
|
251 return NS_ERROR_NOT_INITIALIZED; |
|
252 if (NS_WARN_IF(!thread)) |
|
253 return NS_ERROR_INVALID_ARG; |
|
254 |
|
255 nsRefPtr<nsThread> temp; |
|
256 { |
|
257 MutexAutoLock lock(*mLock); |
|
258 mThreadsByPRThread.Get(thread, getter_AddRefs(temp)); |
|
259 } |
|
260 |
|
261 NS_IF_ADDREF(*result = temp); |
|
262 return NS_OK; |
|
263 } |
|
264 |
|
265 NS_IMETHODIMP |
|
266 nsThreadManager::GetMainThread(nsIThread **result) |
|
267 { |
|
268 // Keep this functioning during Shutdown |
|
269 if (NS_WARN_IF(!mMainThread)) |
|
270 return NS_ERROR_NOT_INITIALIZED; |
|
271 NS_ADDREF(*result = mMainThread); |
|
272 return NS_OK; |
|
273 } |
|
274 |
|
275 NS_IMETHODIMP |
|
276 nsThreadManager::GetCurrentThread(nsIThread **result) |
|
277 { |
|
278 // Keep this functioning during Shutdown |
|
279 if (NS_WARN_IF(!mMainThread)) |
|
280 return NS_ERROR_NOT_INITIALIZED; |
|
281 *result = GetCurrentThread(); |
|
282 if (!*result) |
|
283 return NS_ERROR_OUT_OF_MEMORY; |
|
284 NS_ADDREF(*result); |
|
285 return NS_OK; |
|
286 } |
|
287 |
|
288 NS_IMETHODIMP |
|
289 nsThreadManager::GetIsMainThread(bool *result) |
|
290 { |
|
291 // This method may be called post-Shutdown |
|
292 |
|
293 *result = (PR_GetCurrentThread() == mMainPRThread); |
|
294 return NS_OK; |
|
295 } |
|
296 |
|
297 uint32_t |
|
298 nsThreadManager::GetHighestNumberOfThreads() |
|
299 { |
|
300 MutexAutoLock lock(*mLock); |
|
301 return mHighestNumberOfThreads; |
|
302 } |