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
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/
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
16 * The Original Code is Mozilla code.
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.
23 * Darin Fisher <darin@meer.net>
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.
37 * ***** END LICENSE BLOCK ***** */
40 #include "nsThreadManager.h"
41 #include "nsIClassInfoImpl.h"
42 #include "nsIProgrammingLanguage.h"
43 #include "nsAutoLock.h"
44 #include "nsAutoPtr.h"
47 #include "nsThreadUtilsInternal.h"
50 static PRLogModuleInfo
*sLog
= PR_NewLogModule("nsThread");
52 #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
54 NS_DECL_CI_INTERFACE_GETTER(nsThread
)
56 nsIThreadObserver
* nsThread::sGlobalObserver
;
58 //-----------------------------------------------------------------------------
59 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
62 class nsThreadClassInfo
: public nsIClassInfo
{
64 NS_DECL_ISUPPORTS_INHERITED
// no mRefCnt
67 nsThreadClassInfo() {}
70 static nsThreadClassInfo sThreadClassInfo
;
72 NS_IMETHODIMP_(nsrefcnt
) nsThreadClassInfo::AddRef() { return 2; }
73 NS_IMETHODIMP_(nsrefcnt
) nsThreadClassInfo::Release() { return 1; }
74 NS_IMPL_QUERY_INTERFACE1(nsThreadClassInfo
, nsIClassInfo
)
77 nsThreadClassInfo::GetInterfaces(PRUint32
*count
, nsIID
***array
)
79 return NS_CI_INTERFACE_GETTER_NAME(nsThread
)(count
, array
);
83 nsThreadClassInfo::GetHelperForLanguage(PRUint32 lang
, nsISupports
**result
)
90 nsThreadClassInfo::GetContractID(char **result
)
97 nsThreadClassInfo::GetClassDescription(char **result
)
104 nsThreadClassInfo::GetClassID(nsCID
**result
)
111 nsThreadClassInfo::GetImplementationLanguage(PRUint32
*result
)
113 *result
= nsIProgrammingLanguage::CPLUSPLUS
;
118 nsThreadClassInfo::GetFlags(PRUint32
*result
)
120 *result
= THREADSAFE
;
125 nsThreadClassInfo::GetClassIDNoAlloc(nsCID
*result
)
127 return NS_ERROR_NOT_AVAILABLE
;
130 //-----------------------------------------------------------------------------
132 NS_IMPL_THREADSAFE_ADDREF(nsThread
)
133 NS_IMPL_THREADSAFE_RELEASE(nsThread
)
134 NS_INTERFACE_MAP_BEGIN(nsThread
)
135 NS_INTERFACE_MAP_ENTRY(nsIThread
)
136 NS_INTERFACE_MAP_ENTRY(nsIThreadInternal
)
137 NS_INTERFACE_MAP_ENTRY(nsIEventTarget
)
138 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority
)
139 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIThread
)
140 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
141 foundInterface
= static_cast<nsIClassInfo
*>(&sThreadClassInfo
);
144 NS_IMPL_CI_INTERFACE_GETTER4(nsThread
, nsIThread
, nsIThreadInternal
,
145 nsIEventTarget
, nsISupportsPriority
)
147 //-----------------------------------------------------------------------------
149 class nsThreadStartupEvent
: public nsRunnable
{
151 // Create a new thread startup object.
152 static nsThreadStartupEvent
*Create() {
153 nsThreadStartupEvent
*startup
= new nsThreadStartupEvent();
154 if (startup
&& startup
->mMon
)
156 // Allocation failure
161 // This method does not return until the thread startup object is in the
164 if (mInitialized
) // Maybe avoid locking...
166 nsAutoMonitor
mon(mMon
);
167 while (!mInitialized
)
171 // This method needs to be public to support older compilers (xlC_r on AIX).
172 // It should be called directly as this class type is reference counted.
173 virtual ~nsThreadStartupEvent() {
175 nsAutoMonitor::DestroyMonitor(mMon
);
180 nsAutoMonitor
mon(mMon
);
181 mInitialized
= PR_TRUE
;
186 nsThreadStartupEvent()
187 : mMon(nsAutoMonitor::NewMonitor("xpcom.threadstartup"))
188 , mInitialized(PR_FALSE
) {
195 //-----------------------------------------------------------------------------
197 struct nsThreadShutdownContext
{
198 nsThread
*joiningThread
;
202 // This event is responsible for notifying nsThread::Shutdown that it is time
203 // to call PR_JoinThread.
204 class nsThreadShutdownAckEvent
: public nsRunnable
{
206 nsThreadShutdownAckEvent(nsThreadShutdownContext
*ctx
)
207 : mShutdownContext(ctx
) {
210 mShutdownContext
->shutdownAck
= PR_TRUE
;
214 nsThreadShutdownContext
*mShutdownContext
;
217 // This event is responsible for setting mShutdownContext
218 class nsThreadShutdownEvent
: public nsRunnable
{
220 nsThreadShutdownEvent(nsThread
*thr
, nsThreadShutdownContext
*ctx
)
221 : mThread(thr
), mShutdownContext(ctx
) {
224 mThread
->mShutdownContext
= mShutdownContext
;
228 nsRefPtr
<nsThread
> mThread
;
229 nsThreadShutdownContext
*mShutdownContext
;
232 //-----------------------------------------------------------------------------
235 nsThread::ThreadFunc(void *arg
)
237 nsThread
*self
= static_cast<nsThread
*>(arg
); // strong reference
238 self
->mThread
= PR_GetCurrentThread();
240 // Inform the ThreadManager
241 nsThreadManager::get()->RegisterCurrentThread(self
);
243 // Wait for and process startup event
244 nsCOMPtr
<nsIRunnable
> event
;
245 if (!self
->GetEvent(PR_TRUE
, getter_AddRefs(event
))) {
246 NS_WARNING("failed waiting for thread startup event");
249 event
->Run(); // unblocks nsThread::Init
252 // Now, process incoming events...
253 while (!self
->ShuttingDown())
254 NS_ProcessNextEvent(self
);
256 // Do NS_ProcessPendingEvents but with special handling to set
257 // mEventsAreDoomed atomically with the removal of the last event. The key
258 // invariant here is that we will never permit PutEvent to succeed if the
259 // event would be left in the queue after our final call to
260 // NS_ProcessPendingEvents.
263 nsAutoLock
lock(self
->mLock
);
264 if (!self
->mEvents
->HasPendingEvent()) {
265 // No events in the queue, so we will stop now. Don't let any more
266 // events be added, since they won't be processed. It is critical
267 // that no PutEvent can occur between testing that the event queue is
268 // empty and setting mEventsAreDoomed!
269 self
->mEventsAreDoomed
= PR_TRUE
;
273 NS_ProcessPendingEvents(self
);
276 // Inform the threadmanager that this thread is going away
277 nsThreadManager::get()->UnregisterCurrentThread(self
);
279 // Dispatch shutdown ACK
280 event
= new nsThreadShutdownAckEvent(self
->mShutdownContext
);
281 self
->mShutdownContext
->joiningThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
286 //-----------------------------------------------------------------------------
289 : mLock(PR_NewLock())
290 , mEvents(&mEventsRoot
)
291 , mPriority(PRIORITY_NORMAL
)
294 , mShutdownContext(nsnull
)
295 , mShutdownRequired(PR_FALSE
)
296 , mEventsAreDoomed(PR_FALSE
)
300 nsThread::~nsThread()
303 PR_DestroyLock(mLock
);
309 NS_ENSURE_TRUE(mLock
, NS_ERROR_OUT_OF_MEMORY
);
311 // spawn thread and wait until it is fully setup
312 nsRefPtr
<nsThreadStartupEvent
> startup
= nsThreadStartupEvent::Create();
313 NS_ENSURE_TRUE(startup
, NS_ERROR_OUT_OF_MEMORY
);
317 mShutdownRequired
= PR_TRUE
;
319 // ThreadFunc is responsible for setting mThread
320 PRThread
*thr
= PR_CreateThread(PR_USER_THREAD
, ThreadFunc
, this,
321 PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
,
322 PR_JOINABLE_THREAD
, 0);
325 return NS_ERROR_OUT_OF_MEMORY
;
328 // ThreadFunc will wait for this event to be run before it tries to access
329 // mThread. By delaying insertion of this event into the queue, we ensure
330 // that mThread is set properly.
332 nsAutoLock
lock(mLock
);
333 mEvents
->PutEvent(startup
);
336 // Wait for thread to call ThreadManager::SetupCurrentThread, which completes
337 // initialization of ThreadFunc.
343 nsThread::InitCurrentThread()
345 NS_ENSURE_TRUE(mLock
, NS_ERROR_OUT_OF_MEMORY
);
347 mThread
= PR_GetCurrentThread();
349 nsThreadManager::get()->RegisterCurrentThread(this);
354 nsThread::PutEvent(nsIRunnable
*event
)
357 nsAutoLock
lock(mLock
);
358 if (mEventsAreDoomed
) {
359 NS_WARNING("An event was posted to a thread that will never run it (rejected)");
360 return NS_ERROR_UNEXPECTED
;
362 if (!mEvents
->PutEvent(event
))
363 return NS_ERROR_OUT_OF_MEMORY
;
366 nsCOMPtr
<nsIThreadObserver
> obs
= GetObserver();
368 obs
->OnDispatchedEvent(this);
373 //-----------------------------------------------------------------------------
377 nsThread::Dispatch(nsIRunnable
*event
, PRUint32 flags
)
379 LOG(("THRD(%p) Dispatch [%p %x]\n", this, event
, flags
));
381 NS_ENSURE_ARG_POINTER(event
);
383 if (flags
& DISPATCH_SYNC
) {
384 nsThread
*thread
= nsThreadManager::get()->GetCurrentThread();
385 NS_ENSURE_STATE(thread
);
387 // XXX we should be able to do something better here... we should
388 // be able to monitor the slot occupied by this event and use
389 // that to tell us when the event has been processed.
391 nsRefPtr
<nsThreadSyncDispatch
> wrapper
=
392 new nsThreadSyncDispatch(thread
, event
);
394 return NS_ERROR_OUT_OF_MEMORY
;
395 nsresult rv
= PutEvent(wrapper
);
396 // Don't wait for the event to finish if we didn't dispatch it...
400 while (wrapper
->IsPending())
401 NS_ProcessNextEvent(thread
);
405 NS_ASSERTION(flags
== NS_DISPATCH_NORMAL
, "unexpected dispatch flags");
406 return PutEvent(event
);
410 nsThread::IsOnCurrentThread(PRBool
*result
)
412 *result
= (PR_GetCurrentThread() == mThread
);
416 //-----------------------------------------------------------------------------
420 nsThread::GetPRThread(PRThread
**result
)
429 LOG(("THRD(%p) shutdown\n", this));
431 // XXX If we make this warn, then we hit that warning at xpcom shutdown while
432 // shutting down a thread in a thread pool. That happens b/c the thread
433 // in the thread pool is already shutdown by the thread manager.
437 NS_ENSURE_STATE(mThread
!= PR_GetCurrentThread());
439 // Prevent multiple calls to this method
441 nsAutoLock
lock(mLock
);
442 if (!mShutdownRequired
)
443 return NS_ERROR_UNEXPECTED
;
444 mShutdownRequired
= PR_FALSE
;
447 nsThreadShutdownContext context
;
448 context
.joiningThread
= nsThreadManager::get()->GetCurrentThread();
449 context
.shutdownAck
= PR_FALSE
;
451 // Set mShutdownContext and wake up the thread in case it is waiting for
452 // events to process.
453 nsCOMPtr
<nsIRunnable
> event
= new nsThreadShutdownEvent(this, &context
);
455 return NS_ERROR_OUT_OF_MEMORY
;
456 // XXXroc What if posting the event fails due to OOM?
459 // We could still end up with other events being added after the shutdown
460 // task, but that's okay because we process pending events in ThreadFunc
461 // after setting mShutdownContext just before exiting.
463 // Process events on the current thread until we receive a shutdown ACK.
464 while (!context
.shutdownAck
)
465 NS_ProcessNextEvent(context
.joiningThread
);
467 // Now, it should be safe to join without fear of dead-locking.
469 PR_JoinThread(mThread
);
475 nsThread::HasPendingEvents(PRBool
*result
)
477 NS_ENSURE_STATE(PR_GetCurrentThread() == mThread
);
479 *result
= mEvents
->GetEvent(PR_FALSE
, nsnull
);
484 nsThread::ProcessNextEvent(PRBool mayWait
, PRBool
*result
)
486 LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait
, mRunningEvent
));
488 NS_ENSURE_STATE(PR_GetCurrentThread() == mThread
);
490 PRBool notifyGlobalObserver
= (sGlobalObserver
!= nsnull
);
491 if (notifyGlobalObserver
)
492 sGlobalObserver
->OnProcessNextEvent(this, mayWait
&& !ShuttingDown(),
495 nsCOMPtr
<nsIThreadObserver
> obs
= mObserver
;
497 obs
->OnProcessNextEvent(this, mayWait
&& !ShuttingDown(), mRunningEvent
);
499 // If we are shutting down, then do not wait for new events.
500 nsCOMPtr
<nsIRunnable
> event
;
501 mEvents
->GetEvent(mayWait
&& !ShuttingDown(), getter_AddRefs(event
));
503 *result
= (event
.get() != nsnull
);
508 LOG(("THRD(%p) running [%p]\n", this, event
.get()));
512 } else if (mayWait
) {
513 NS_ASSERTION(ShuttingDown(), "This should only happen when shutting down");
514 rv
= NS_ERROR_UNEXPECTED
;
518 obs
->AfterProcessNextEvent(this, mRunningEvent
);
520 if (notifyGlobalObserver
&& sGlobalObserver
)
521 sGlobalObserver
->AfterProcessNextEvent(this, mRunningEvent
);
526 //-----------------------------------------------------------------------------
527 // nsISupportsPriority
530 nsThread::GetPriority(PRInt32
*priority
)
532 *priority
= mPriority
;
537 nsThread::SetPriority(PRInt32 priority
)
539 NS_ENSURE_STATE(mThread
);
541 // NSPR defines the following four thread priorities:
543 // PR_PRIORITY_NORMAL
545 // PR_PRIORITY_URGENT
546 // We map the priority values defined on nsISupportsPriority to these values.
548 mPriority
= priority
;
550 PRThreadPriority pri
;
551 if (mPriority
<= PRIORITY_HIGHEST
) {
552 pri
= PR_PRIORITY_URGENT
;
553 } else if (mPriority
< PRIORITY_NORMAL
) {
554 pri
= PR_PRIORITY_HIGH
;
555 } else if (mPriority
> PRIORITY_NORMAL
) {
556 pri
= PR_PRIORITY_LOW
;
558 pri
= PR_PRIORITY_NORMAL
;
560 PR_SetThreadPriority(mThread
, pri
);
566 nsThread::AdjustPriority(PRInt32 delta
)
568 return SetPriority(mPriority
+ delta
);
571 //-----------------------------------------------------------------------------
575 nsThread::GetObserver(nsIThreadObserver
**obs
)
577 nsAutoLock
lock(mLock
);
578 NS_IF_ADDREF(*obs
= mObserver
);
583 nsThread::SetObserver(nsIThreadObserver
*obs
)
585 NS_ENSURE_STATE(PR_GetCurrentThread() == mThread
);
587 nsAutoLock
lock(mLock
);
593 nsThread::PushEventQueue(nsIThreadEventFilter
*filter
)
595 nsChainedEventQueue
*queue
= new nsChainedEventQueue(filter
);
596 if (!queue
|| !queue
->IsInitialized()) {
598 return NS_ERROR_OUT_OF_MEMORY
;
601 nsAutoLock
lock(mLock
);
602 queue
->mNext
= mEvents
;
608 nsThread::PopEventQueue()
610 nsAutoLock
lock(mLock
);
612 // Make sure we do not pop too many!
613 NS_ENSURE_STATE(mEvents
!= &mEventsRoot
);
615 nsChainedEventQueue
*queue
= mEvents
;
616 mEvents
= mEvents
->mNext
;
618 nsCOMPtr
<nsIRunnable
> event
;
619 while (queue
->GetEvent(PR_FALSE
, getter_AddRefs(event
)))
620 mEvents
->PutEvent(event
);
628 nsThread::nsChainedEventQueue::PutEvent(nsIRunnable
*event
)
631 if (!mFilter
|| mFilter
->AcceptEvent(event
)) {
632 val
= mQueue
.PutEvent(event
);
634 val
= mNext
->PutEvent(event
);
639 //-----------------------------------------------------------------------------
642 nsThreadSyncDispatch::Run()
647 // unblock the origin thread
648 mOrigin
->Dispatch(this, NS_DISPATCH_NORMAL
);
654 NS_SetGlobalThreadObserver(nsIThreadObserver
* aObserver
)
656 if (aObserver
&& nsThread::sGlobalObserver
) {
657 return NS_ERROR_NOT_AVAILABLE
;
660 if (!NS_IsMainThread()) {
661 return NS_ERROR_UNEXPECTED
;
664 nsThread::sGlobalObserver
= aObserver
;