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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
18 : * The Initial Developer of the Original Code is Google Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 2006
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsIClassInfoImpl.h"
40 : #include "nsThreadPool.h"
41 : #include "nsThreadManager.h"
42 : #include "nsThread.h"
43 : #include "nsMemory.h"
44 : #include "nsAutoPtr.h"
45 : #include "prinrval.h"
46 : #include "prlog.h"
47 :
48 : using namespace mozilla;
49 :
50 : #ifdef PR_LOGGING
51 1464 : static PRLogModuleInfo *sLog = PR_NewLogModule("nsThreadPool");
52 : #endif
53 : #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
54 :
55 : // DESIGN:
56 : // o Allocate anonymous threads.
57 : // o Use nsThreadPool::Run as the main routine for each thread.
58 : // o Each thread waits on the event queue's monitor, checking for
59 : // pending events and rescheduling itself as an idle thread.
60 :
61 : #define DEFAULT_THREAD_LIMIT 4
62 : #define DEFAULT_IDLE_THREAD_LIMIT 1
63 : #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
64 :
65 3626 : NS_IMPL_THREADSAFE_ADDREF(nsThreadPool)
66 5186 : NS_IMPL_THREADSAFE_RELEASE(nsThreadPool)
67 : NS_IMPL_CLASSINFO(nsThreadPool, NULL, nsIClassInfo::THREADSAFE,
68 : NS_THREADPOOL_CID)
69 1880 : NS_IMPL_QUERY_INTERFACE3_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
70 0 : nsIRunnable)
71 0 : NS_IMPL_CI_INTERFACE_GETTER2(nsThreadPool, nsIThreadPool, nsIEventTarget)
72 :
73 1560 : nsThreadPool::nsThreadPool()
74 : : mThreadLimit(DEFAULT_THREAD_LIMIT)
75 : , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
76 1560 : , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
77 : , mIdleCount(0)
78 3120 : , mShutdown(false)
79 : {
80 1560 : }
81 :
82 3120 : nsThreadPool::~nsThreadPool()
83 : {
84 1560 : Shutdown();
85 1560 : }
86 :
87 : nsresult
88 1263 : nsThreadPool::PutEvent(nsIRunnable *event)
89 : {
90 : // Avoid spawning a new thread while holding the event queue lock...
91 :
92 1263 : bool spawnThread = false;
93 : {
94 2526 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
95 :
96 1263 : LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
97 : mThreadLimit));
98 1263 : NS_ASSERTION(mIdleCount <= (PRUint32) mThreads.Count(), "oops");
99 :
100 : // Make sure we have a thread to service this event.
101 1263 : if (mIdleCount == 0 && mThreads.Count() < (PRInt32) mThreadLimit)
102 186 : spawnThread = true;
103 :
104 1263 : mEvents.PutEvent(event);
105 : }
106 :
107 1263 : LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
108 1263 : if (!spawnThread)
109 1077 : return NS_OK;
110 :
111 372 : nsCOMPtr<nsIThread> thread;
112 186 : nsThreadManager::get()->NewThread(0,
113 : nsIThreadManager::DEFAULT_STACK_SIZE,
114 186 : getter_AddRefs(thread));
115 186 : NS_ENSURE_STATE(thread);
116 :
117 186 : bool killThread = false;
118 : {
119 372 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
120 186 : if (mThreads.Count() < (PRInt32) mThreadLimit) {
121 186 : mThreads.AppendObject(thread);
122 : } else {
123 0 : killThread = true; // okay, we don't need this thread anymore
124 : }
125 : }
126 186 : LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
127 186 : if (killThread) {
128 0 : thread->Shutdown();
129 : } else {
130 186 : thread->Dispatch(this, NS_DISPATCH_NORMAL);
131 : }
132 :
133 186 : return NS_OK;
134 : }
135 :
136 : void
137 29 : nsThreadPool::ShutdownThread(nsIThread *thread)
138 : {
139 29 : LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread));
140 :
141 : // This method is responsible for calling Shutdown on |thread|. This must be
142 : // done from some other thread, so we use the main thread of the application.
143 :
144 29 : NS_ASSERTION(!NS_IsMainThread(), "wrong thread");
145 :
146 58 : nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown);
147 29 : NS_DispatchToMainThread(r);
148 29 : }
149 :
150 : NS_IMETHODIMP
151 186 : nsThreadPool::Run()
152 : {
153 186 : LOG(("THRD-P(%p) enter\n", this));
154 :
155 372 : nsCOMPtr<nsIThread> current;
156 186 : nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current));
157 :
158 186 : bool shutdownThreadOnExit = false;
159 186 : bool exitThread = false;
160 186 : bool wasIdle = false;
161 : PRIntervalTime idleSince;
162 :
163 372 : nsCOMPtr<nsIThreadPoolListener> listener;
164 : {
165 372 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
166 186 : listener = mListener;
167 : }
168 :
169 186 : if (listener) {
170 0 : listener->OnThreadCreated();
171 : }
172 :
173 3015 : do {
174 6030 : nsCOMPtr<nsIRunnable> event;
175 : {
176 6030 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
177 3015 : if (!mEvents.GetPendingEvent(getter_AddRefs(event))) {
178 1752 : PRIntervalTime now = PR_IntervalNow();
179 1752 : PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
180 :
181 : // If we are shutting down, then don't keep any idle threads
182 1752 : if (mShutdown) {
183 157 : exitThread = true;
184 : } else {
185 1595 : if (wasIdle) {
186 : // if too many idle threads or idle for too long, then bail.
187 342 : if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout)
188 0 : exitThread = true;
189 : } else {
190 : // if would be too many idle threads...
191 1253 : if (mIdleCount == mIdleThreadLimit) {
192 29 : exitThread = true;
193 : } else {
194 1224 : ++mIdleCount;
195 1224 : idleSince = now;
196 1224 : wasIdle = true;
197 : }
198 : }
199 : }
200 :
201 1752 : if (exitThread) {
202 186 : if (wasIdle)
203 156 : --mIdleCount;
204 186 : shutdownThreadOnExit = mThreads.RemoveObject(current);
205 : } else {
206 1566 : PRIntervalTime delta = timeout - (now - idleSince);
207 1566 : LOG(("THRD-P(%p) waiting [%d]\n", this, delta));
208 1566 : mon.Wait(delta);
209 : }
210 1263 : } else if (wasIdle) {
211 1068 : wasIdle = false;
212 1068 : --mIdleCount;
213 : }
214 : }
215 3015 : if (event) {
216 1263 : LOG(("THRD-P(%p) running [%p]\n", this, event.get()));
217 1263 : event->Run();
218 : }
219 3015 : } while (!exitThread);
220 :
221 186 : if (listener) {
222 0 : listener->OnThreadShuttingDown();
223 : }
224 :
225 186 : if (shutdownThreadOnExit) {
226 29 : ShutdownThread(current);
227 : }
228 :
229 186 : LOG(("THRD-P(%p) leave\n", this));
230 186 : return NS_OK;
231 : }
232 :
233 : NS_IMETHODIMP
234 1266 : nsThreadPool::Dispatch(nsIRunnable *event, PRUint32 flags)
235 : {
236 1266 : LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags));
237 :
238 1266 : NS_ENSURE_STATE(!mShutdown);
239 :
240 1263 : if (flags & DISPATCH_SYNC) {
241 0 : nsCOMPtr<nsIThread> thread;
242 0 : nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread));
243 0 : NS_ENSURE_STATE(thread);
244 :
245 : nsRefPtr<nsThreadSyncDispatch> wrapper =
246 0 : new nsThreadSyncDispatch(thread, event);
247 0 : PutEvent(wrapper);
248 :
249 0 : while (wrapper->IsPending())
250 0 : NS_ProcessNextEvent(thread);
251 : } else {
252 1263 : NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
253 1263 : PutEvent(event);
254 : }
255 1263 : return NS_OK;
256 : }
257 :
258 : NS_IMETHODIMP
259 0 : nsThreadPool::IsOnCurrentThread(bool *result)
260 : {
261 : // No one should be calling this method. If this assertion gets hit, then we
262 : // need to think carefully about what this method should be returning.
263 0 : NS_NOTREACHED("implement me");
264 :
265 0 : *result = false;
266 0 : return NS_OK;
267 : }
268 :
269 : NS_IMETHODIMP
270 3120 : nsThreadPool::Shutdown()
271 : {
272 6240 : nsCOMArray<nsIThread> threads;
273 6240 : nsCOMPtr<nsIThreadPoolListener> listener;
274 : {
275 6240 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
276 3120 : mShutdown = true;
277 3120 : mon.NotifyAll();
278 :
279 3120 : threads.AppendObjects(mThreads);
280 3120 : mThreads.Clear();
281 :
282 : // Swap in a null listener so that we release the listener at the end of
283 : // this method. The listener will be kept alive as long as the other threads
284 : // that were created when it was set.
285 3120 : mListener.swap(listener);
286 : }
287 :
288 : // It's important that we shutdown the threads while outside the event queue
289 : // monitor. Otherwise, we could end up dead-locking.
290 :
291 3277 : for (PRInt32 i = 0; i < threads.Count(); ++i)
292 157 : threads[i]->Shutdown();
293 :
294 3120 : return NS_OK;
295 : }
296 :
297 : NS_IMETHODIMP
298 0 : nsThreadPool::GetThreadLimit(PRUint32 *value)
299 : {
300 0 : *value = mThreadLimit;
301 0 : return NS_OK;
302 : }
303 :
304 : NS_IMETHODIMP
305 1560 : nsThreadPool::SetThreadLimit(PRUint32 value)
306 : {
307 3120 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
308 1560 : mThreadLimit = value;
309 1560 : if (mIdleThreadLimit > mThreadLimit)
310 0 : mIdleThreadLimit = mThreadLimit;
311 1560 : mon.NotifyAll(); // wake up threads so they observe this change
312 1560 : return NS_OK;
313 : }
314 :
315 : NS_IMETHODIMP
316 0 : nsThreadPool::GetIdleThreadLimit(PRUint32 *value)
317 : {
318 0 : *value = mIdleThreadLimit;
319 0 : return NS_OK;
320 : }
321 :
322 : NS_IMETHODIMP
323 1560 : nsThreadPool::SetIdleThreadLimit(PRUint32 value)
324 : {
325 3120 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
326 1560 : mIdleThreadLimit = value;
327 1560 : if (mIdleThreadLimit > mThreadLimit)
328 1426 : mIdleThreadLimit = mThreadLimit;
329 1560 : mon.NotifyAll(); // wake up threads so they observe this change
330 1560 : return NS_OK;
331 : }
332 :
333 : NS_IMETHODIMP
334 0 : nsThreadPool::GetIdleThreadTimeout(PRUint32 *value)
335 : {
336 0 : *value = mIdleThreadTimeout;
337 0 : return NS_OK;
338 : }
339 :
340 : NS_IMETHODIMP
341 1560 : nsThreadPool::SetIdleThreadTimeout(PRUint32 value)
342 : {
343 3120 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
344 1560 : mIdleThreadTimeout = value;
345 1560 : mon.NotifyAll(); // wake up threads so they observe this change
346 1560 : return NS_OK;
347 : }
348 :
349 : NS_IMETHODIMP
350 0 : nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
351 : {
352 0 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
353 0 : NS_IF_ADDREF(*aListener = mListener);
354 0 : return NS_OK;
355 : }
356 :
357 : NS_IMETHODIMP
358 0 : nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
359 : {
360 0 : nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
361 : {
362 0 : ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
363 0 : mListener.swap(swappedListener);
364 : }
365 0 : return NS_OK;
366 4392 : }
|