1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
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 Indexed Database.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * The Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Ben Turner <bent.mozilla@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "LazyIdleThread.h"
41 :
42 : #include "nsIObserverService.h"
43 :
44 : #include "nsComponentManagerUtils.h"
45 : #include "nsServiceManagerUtils.h"
46 : #include "nsThreadUtils.h"
47 : #include "mozilla/Services.h"
48 :
49 : #ifdef DEBUG
50 : #define ASSERT_OWNING_THREAD() \
51 : PR_BEGIN_MACRO \
52 : nsIThread* currentThread = NS_GetCurrentThread(); \
53 : if (currentThread) { \
54 : nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread)); \
55 : nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread)); \
56 : NS_ASSERTION(current == test, "Wrong thread!"); \
57 : } \
58 : PR_END_MACRO
59 : #else
60 : #define ASSERT_OWNING_THREAD() /* nothing */
61 : #endif
62 :
63 : namespace mozilla {
64 :
65 54 : LazyIdleThread::LazyIdleThread(PRUint32 aIdleTimeoutMS,
66 : ShutdownMethod aShutdownMethod,
67 : nsIObserver* aIdleObserver)
68 : : mMutex("LazyIdleThread::mMutex"),
69 : mOwningThread(NS_GetCurrentThread()),
70 : mIdleObserver(aIdleObserver),
71 : mQueuedRunnables(nsnull),
72 : mIdleTimeoutMS(aIdleTimeoutMS),
73 : mPendingEventCount(0),
74 : mIdleNotificationCount(0),
75 : mShutdownMethod(aShutdownMethod),
76 : mShutdown(false),
77 : mThreadIsShuttingDown(false),
78 54 : mIdleTimeoutEnabled(true)
79 : {
80 54 : NS_ASSERTION(mOwningThread, "This should never fail!");
81 54 : }
82 :
83 108 : LazyIdleThread::~LazyIdleThread()
84 : {
85 54 : ASSERT_OWNING_THREAD();
86 :
87 54 : Shutdown();
88 54 : }
89 :
90 : void
91 0 : LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver)
92 : {
93 0 : ASSERT_OWNING_THREAD();
94 :
95 0 : if (mShutdown) {
96 0 : NS_WARN_IF_FALSE(!aObserver,
97 : "Setting an observer after Shutdown was called!");
98 0 : return;
99 : }
100 :
101 0 : mIdleObserver = aObserver;
102 : }
103 :
104 : void
105 0 : LazyIdleThread::DisableIdleTimeout()
106 : {
107 0 : ASSERT_OWNING_THREAD();
108 0 : if (!mIdleTimeoutEnabled) {
109 0 : return;
110 : }
111 0 : mIdleTimeoutEnabled = false;
112 :
113 0 : if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
114 0 : NS_WARNING("Failed to cancel timer!");
115 : }
116 :
117 0 : MutexAutoLock lock(mMutex);
118 :
119 : // Pretend we have a pending event to keep the idle timer from firing.
120 0 : NS_ASSERTION(mPendingEventCount < PR_UINT32_MAX, "Way too many!");
121 0 : mPendingEventCount++;
122 : }
123 :
124 : void
125 0 : LazyIdleThread::EnableIdleTimeout()
126 : {
127 0 : ASSERT_OWNING_THREAD();
128 0 : if (mIdleTimeoutEnabled) {
129 0 : return;
130 : }
131 0 : mIdleTimeoutEnabled = true;
132 :
133 : {
134 0 : MutexAutoLock lock(mMutex);
135 :
136 0 : NS_ASSERTION(mPendingEventCount, "Mismatched calls to observer methods!");
137 0 : --mPendingEventCount;
138 : }
139 :
140 0 : if (mThread) {
141 0 : nsCOMPtr<nsIRunnable> runnable(new nsRunnable());
142 0 : if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
143 0 : NS_WARNING("Failed to dispatch!");
144 : }
145 : }
146 : }
147 :
148 : void
149 138 : LazyIdleThread::PreDispatch()
150 : {
151 276 : MutexAutoLock lock(mMutex);
152 :
153 138 : NS_ASSERTION(mPendingEventCount < PR_UINT32_MAX, "Way too many!");
154 138 : mPendingEventCount++;
155 138 : }
156 :
157 : nsresult
158 84 : LazyIdleThread::EnsureThread()
159 : {
160 84 : ASSERT_OWNING_THREAD();
161 :
162 84 : if (mShutdown) {
163 0 : return NS_ERROR_UNEXPECTED;
164 : }
165 :
166 84 : if (mThread) {
167 30 : return NS_OK;
168 : }
169 :
170 54 : NS_ASSERTION(!mPendingEventCount, "Shouldn't have events yet!");
171 54 : NS_ASSERTION(!mIdleNotificationCount, "Shouldn't have idle events yet!");
172 54 : NS_ASSERTION(!mIdleTimer, "Should have killed this long ago!");
173 54 : NS_ASSERTION(!mThreadIsShuttingDown, "Should have cleared that!");
174 :
175 : nsresult rv;
176 :
177 54 : if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
178 : nsCOMPtr<nsIObserverService> obs =
179 0 : do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
180 0 : NS_ENSURE_SUCCESS(rv, rv);
181 :
182 0 : rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
183 0 : NS_ENSURE_SUCCESS(rv, rv);
184 : }
185 :
186 54 : mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
187 54 : NS_ENSURE_TRUE(mIdleTimer, NS_ERROR_FAILURE);
188 :
189 : nsCOMPtr<nsIRunnable> runnable =
190 108 : NS_NewRunnableMethod(this, &LazyIdleThread::InitThread);
191 54 : NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE);
192 :
193 54 : rv = NS_NewThread(getter_AddRefs(mThread), runnable);
194 54 : NS_ENSURE_SUCCESS(rv, rv);
195 :
196 54 : return NS_OK;
197 : }
198 :
199 : void
200 54 : LazyIdleThread::InitThread()
201 : {
202 : // Happens on mThread but mThread may not be set yet...
203 :
204 108 : nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
205 54 : NS_ASSERTION(thread, "This should always succeed!");
206 :
207 54 : if (NS_FAILED(thread->SetObserver(this))) {
208 0 : NS_WARNING("Failed to set thread observer!");
209 : }
210 54 : }
211 :
212 : void
213 54 : LazyIdleThread::CleanupThread()
214 : {
215 108 : nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
216 54 : NS_ASSERTION(thread, "This should always succeed!");
217 :
218 54 : if (NS_FAILED(thread->SetObserver(nsnull))) {
219 0 : NS_WARNING("Failed to set thread observer!");
220 : }
221 :
222 108 : MutexAutoLock lock(mMutex);
223 :
224 54 : NS_ASSERTION(!mThreadIsShuttingDown, "Shouldn't be true ever!");
225 54 : mThreadIsShuttingDown = true;
226 54 : }
227 :
228 : void
229 80 : LazyIdleThread::ScheduleTimer()
230 : {
231 80 : ASSERT_OWNING_THREAD();
232 :
233 : bool shouldSchedule;
234 : {
235 160 : MutexAutoLock lock(mMutex);
236 :
237 80 : NS_ASSERTION(mIdleNotificationCount, "Should have at least one!");
238 80 : --mIdleNotificationCount;
239 :
240 80 : shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
241 : }
242 :
243 80 : if (NS_FAILED(mIdleTimer->Cancel())) {
244 0 : NS_WARNING("Failed to cancel timer!");
245 : }
246 :
247 160 : if (shouldSchedule &&
248 80 : NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS,
249 : nsITimer::TYPE_ONE_SHOT))) {
250 0 : NS_WARNING("Failed to schedule timer!");
251 : }
252 80 : }
253 :
254 : nsresult
255 108 : LazyIdleThread::ShutdownThread()
256 : {
257 108 : ASSERT_OWNING_THREAD();
258 :
259 : // Before calling Shutdown() on the real thread we need to put a queue in
260 : // place in case a runnable is posted to the thread while it's in the
261 : // process of shutting down. This will be our queue.
262 216 : nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
263 :
264 : nsresult rv;
265 :
266 108 : if (mThread) {
267 54 : if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
268 : nsCOMPtr<nsIObserverService> obs =
269 0 : mozilla::services::GetObserverService();
270 0 : NS_WARN_IF_FALSE(obs, "Failed to get observer service!");
271 :
272 0 : if (obs &&
273 0 : NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
274 0 : NS_WARNING("Failed to remove observer!");
275 : }
276 : }
277 :
278 54 : if (mIdleObserver) {
279 : mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
280 0 : nsnull);
281 : }
282 :
283 : #ifdef DEBUG
284 : {
285 108 : MutexAutoLock lock(mMutex);
286 54 : NS_ASSERTION(!mThreadIsShuttingDown, "Huh?!");
287 : }
288 : #endif
289 :
290 : nsCOMPtr<nsIRunnable> runnable =
291 108 : NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread);
292 54 : NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE);
293 :
294 54 : PreDispatch();
295 :
296 54 : rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
297 54 : NS_ENSURE_SUCCESS(rv, rv);
298 :
299 : // Put the temporary queue in place before calling Shutdown().
300 54 : mQueuedRunnables = &queuedRunnables;
301 :
302 54 : if (NS_FAILED(mThread->Shutdown())) {
303 0 : NS_ERROR("Failed to shutdown the thread!");
304 : }
305 :
306 : // Now unset the queue.
307 54 : mQueuedRunnables = nsnull;
308 :
309 54 : mThread = nsnull;
310 :
311 : {
312 162 : MutexAutoLock lock(mMutex);
313 :
314 54 : NS_ASSERTION(!mPendingEventCount, "Huh?!");
315 54 : NS_ASSERTION(!mIdleNotificationCount, "Huh?!");
316 54 : NS_ASSERTION(mThreadIsShuttingDown, "Huh?!");
317 54 : mThreadIsShuttingDown = false;
318 : }
319 : }
320 :
321 108 : if (mIdleTimer) {
322 54 : rv = mIdleTimer->Cancel();
323 54 : NS_ENSURE_SUCCESS(rv, rv);
324 :
325 54 : mIdleTimer = nsnull;
326 : }
327 :
328 : // If our temporary queue has any runnables then we need to dispatch them.
329 108 : if (queuedRunnables.Length()) {
330 : // If the thread manager has gone away then these runnables will never run.
331 0 : if (mShutdown) {
332 0 : NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
333 0 : return NS_OK;
334 : }
335 :
336 : // Re-dispatch the queued runnables.
337 0 : for (PRUint32 index = 0; index < queuedRunnables.Length(); index++) {
338 0 : nsCOMPtr<nsIRunnable> runnable;
339 0 : runnable.swap(queuedRunnables[index]);
340 0 : NS_ASSERTION(runnable, "Null runnable?!");
341 :
342 0 : if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
343 0 : NS_ERROR("Failed to re-dispatch queued runnable!");
344 : }
345 : }
346 : }
347 :
348 108 : return NS_OK;
349 : }
350 :
351 : void
352 54 : LazyIdleThread::SelfDestruct()
353 : {
354 54 : NS_ASSERTION(mRefCnt == 1, "Bad refcount!");
355 54 : delete this;
356 54 : }
357 :
358 860 : NS_IMPL_THREADSAFE_ADDREF(LazyIdleThread)
359 :
360 : NS_IMETHODIMP_(nsrefcnt)
361 860 : LazyIdleThread::Release()
362 : {
363 860 : nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt);
364 860 : NS_LOG_RELEASE(this, count, "LazyIdleThread");
365 :
366 860 : if (!count) {
367 : // Stabilize refcount.
368 54 : mRefCnt = 1;
369 :
370 : nsCOMPtr<nsIRunnable> runnable =
371 108 : NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct);
372 54 : NS_WARN_IF_FALSE(runnable, "Couldn't make runnable!");
373 :
374 54 : if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
375 54 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
376 : // The only way this could fail is if we're in shutdown, and in that case
377 : // threads should have been joined already. Deleting here isn't dangerous
378 : // anymore because we won't spin the event loop waiting to join the
379 : // thread.
380 54 : SelfDestruct();
381 : }
382 : }
383 :
384 860 : return count;
385 : }
386 :
387 227 : NS_IMPL_THREADSAFE_QUERY_INTERFACE5(LazyIdleThread, nsIThread,
388 : nsIEventTarget,
389 : nsITimerCallback,
390 : nsIThreadObserver,
391 : nsIObserver)
392 :
393 : NS_IMETHODIMP
394 84 : LazyIdleThread::Dispatch(nsIRunnable* aEvent,
395 : PRUint32 aFlags)
396 : {
397 84 : ASSERT_OWNING_THREAD();
398 :
399 : // LazyIdleThread can't always support synchronous dispatch currently.
400 84 : NS_ENSURE_TRUE(aFlags == NS_DISPATCH_NORMAL, NS_ERROR_NOT_IMPLEMENTED);
401 :
402 : // If our thread is shutting down then we can't actually dispatch right now.
403 : // Queue this runnable for later.
404 84 : if (UseRunnableQueue()) {
405 0 : mQueuedRunnables->AppendElement(aEvent);
406 0 : return NS_OK;
407 : }
408 :
409 84 : nsresult rv = EnsureThread();
410 84 : NS_ENSURE_SUCCESS(rv, rv);
411 :
412 84 : PreDispatch();
413 :
414 84 : return mThread->Dispatch(aEvent, aFlags);
415 : }
416 :
417 : NS_IMETHODIMP
418 152 : LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread)
419 : {
420 152 : if (mThread) {
421 152 : return mThread->IsOnCurrentThread(aIsOnCurrentThread);
422 : }
423 :
424 0 : *aIsOnCurrentThread = false;
425 0 : return NS_OK;
426 : }
427 :
428 : NS_IMETHODIMP
429 0 : LazyIdleThread::GetPRThread(PRThread** aPRThread)
430 : {
431 0 : if (mThread) {
432 0 : return mThread->GetPRThread(aPRThread);
433 : }
434 :
435 0 : *aPRThread = nsnull;
436 0 : return NS_ERROR_NOT_AVAILABLE;
437 : }
438 :
439 : NS_IMETHODIMP
440 108 : LazyIdleThread::Shutdown()
441 : {
442 108 : ASSERT_OWNING_THREAD();
443 :
444 108 : mShutdown = true;
445 :
446 108 : nsresult rv = ShutdownThread();
447 108 : NS_ASSERTION(!mThread, "Should have destroyed this by now!");
448 :
449 108 : mIdleObserver = nsnull;
450 :
451 108 : NS_ENSURE_SUCCESS(rv, rv);
452 :
453 108 : return NS_OK;
454 : }
455 :
456 : NS_IMETHODIMP
457 0 : LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
458 : {
459 : // This is only supposed to be called from the thread itself so it's not
460 : // implemented here.
461 0 : NS_NOTREACHED("Shouldn't ever call this!");
462 0 : return NS_ERROR_UNEXPECTED;
463 : }
464 :
465 : NS_IMETHODIMP
466 0 : LazyIdleThread::ProcessNextEvent(bool aMayWait,
467 : bool* aEventWasProcessed)
468 : {
469 : // This is only supposed to be called from the thread itself so it's not
470 : // implemented here.
471 0 : NS_NOTREACHED("Shouldn't ever call this!");
472 0 : return NS_ERROR_UNEXPECTED;
473 : }
474 :
475 : NS_IMETHODIMP
476 0 : LazyIdleThread::Notify(nsITimer* aTimer)
477 : {
478 0 : ASSERT_OWNING_THREAD();
479 :
480 : {
481 0 : MutexAutoLock lock(mMutex);
482 :
483 0 : if (mPendingEventCount || mIdleNotificationCount) {
484 : // Another event was scheduled since this timer was set. Don't do
485 : // anything and wait for the timer to fire again.
486 0 : return NS_OK;
487 : }
488 : }
489 :
490 0 : nsresult rv = ShutdownThread();
491 0 : NS_ENSURE_SUCCESS(rv, rv);
492 :
493 0 : return NS_OK;
494 : }
495 :
496 : NS_IMETHODIMP
497 111 : LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
498 : {
499 111 : NS_ASSERTION(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
500 111 : return NS_OK;
501 : }
502 :
503 : NS_IMETHODIMP
504 138 : LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
505 : bool /* aMayWait */,
506 : PRUint32 /* aRecursionDepth */)
507 : {
508 138 : return NS_OK;
509 : }
510 :
511 : NS_IMETHODIMP
512 138 : LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
513 : PRUint32 /* aRecursionDepth */)
514 : {
515 : bool shouldNotifyIdle;
516 : {
517 276 : MutexAutoLock lock(mMutex);
518 :
519 138 : NS_ASSERTION(mPendingEventCount, "Mismatched calls to observer methods!");
520 138 : --mPendingEventCount;
521 :
522 138 : if (mThreadIsShuttingDown) {
523 : // We're shutting down, no need to fire any timer.
524 54 : return NS_OK;
525 : }
526 :
527 84 : shouldNotifyIdle = !mPendingEventCount;
528 84 : if (shouldNotifyIdle) {
529 80 : NS_ASSERTION(mIdleNotificationCount < PR_UINT32_MAX, "Way too many!");
530 80 : mIdleNotificationCount++;
531 : }
532 : }
533 :
534 84 : if (shouldNotifyIdle) {
535 : nsCOMPtr<nsIRunnable> runnable =
536 160 : NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer);
537 80 : NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE);
538 :
539 80 : nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
540 80 : NS_ENSURE_SUCCESS(rv, rv);
541 : }
542 :
543 84 : return NS_OK;
544 : }
545 :
546 : NS_IMETHODIMP
547 0 : LazyIdleThread::Observe(nsISupports* /* aSubject */,
548 : const char* aTopic,
549 : const PRUnichar* /* aData */)
550 : {
551 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
552 0 : NS_ASSERTION(mShutdownMethod == AutomaticShutdown,
553 : "Should not receive notifications if not AutomaticShutdown!");
554 0 : NS_ASSERTION(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
555 :
556 0 : Shutdown();
557 0 : return NS_OK;
558 : }
559 :
560 : } // namespace mozilla
|