Backed out changeset fa432b23baa5. (Backing out bug 454781 to investigate mochitest...
[wine-gecko.git] / xpcom / threads / nsThread.cpp
blob9631c6be6ddbb1142b83ab14b0a9f0a44e783068
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
14 * License.
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.
22 * Contributor(s):
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 ***** */
39 #include "nsThread.h"
40 #include "nsThreadManager.h"
41 #include "nsIClassInfoImpl.h"
42 #include "nsIProgrammingLanguage.h"
43 #include "nsAutoLock.h"
44 #include "nsAutoPtr.h"
45 #include "nsCOMPtr.h"
46 #include "prlog.h"
47 #include "nsThreadUtilsInternal.h"
49 #ifdef PR_LOGGING
50 static PRLogModuleInfo *sLog = PR_NewLogModule("nsThread");
51 #endif
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
60 // somewhat manually.
62 class nsThreadClassInfo : public nsIClassInfo {
63 public:
64 NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
65 NS_DECL_NSICLASSINFO
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)
76 NS_IMETHODIMP
77 nsThreadClassInfo::GetInterfaces(PRUint32 *count, nsIID ***array)
79 return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array);
82 NS_IMETHODIMP
83 nsThreadClassInfo::GetHelperForLanguage(PRUint32 lang, nsISupports **result)
85 *result = nsnull;
86 return NS_OK;
89 NS_IMETHODIMP
90 nsThreadClassInfo::GetContractID(char **result)
92 *result = nsnull;
93 return NS_OK;
96 NS_IMETHODIMP
97 nsThreadClassInfo::GetClassDescription(char **result)
99 *result = nsnull;
100 return NS_OK;
103 NS_IMETHODIMP
104 nsThreadClassInfo::GetClassID(nsCID **result)
106 *result = nsnull;
107 return NS_OK;
110 NS_IMETHODIMP
111 nsThreadClassInfo::GetImplementationLanguage(PRUint32 *result)
113 *result = nsIProgrammingLanguage::CPLUSPLUS;
114 return NS_OK;
117 NS_IMETHODIMP
118 nsThreadClassInfo::GetFlags(PRUint32 *result)
120 *result = THREADSAFE;
121 return NS_OK;
124 NS_IMETHODIMP
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);
142 } else
143 NS_INTERFACE_MAP_END
144 NS_IMPL_CI_INTERFACE_GETTER4(nsThread, nsIThread, nsIThreadInternal,
145 nsIEventTarget, nsISupportsPriority)
147 //-----------------------------------------------------------------------------
149 class nsThreadStartupEvent : public nsRunnable {
150 public:
151 // Create a new thread startup object.
152 static nsThreadStartupEvent *Create() {
153 nsThreadStartupEvent *startup = new nsThreadStartupEvent();
154 if (startup && startup->mMon)
155 return startup;
156 // Allocation failure
157 delete startup;
158 return nsnull;
161 // This method does not return until the thread startup object is in the
162 // completion state.
163 void Wait() {
164 if (mInitialized) // Maybe avoid locking...
165 return;
166 nsAutoMonitor mon(mMon);
167 while (!mInitialized)
168 mon.Wait();
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() {
174 if (mMon)
175 nsAutoMonitor::DestroyMonitor(mMon);
178 private:
179 NS_IMETHOD Run() {
180 nsAutoMonitor mon(mMon);
181 mInitialized = PR_TRUE;
182 mon.Notify();
183 return NS_OK;
186 nsThreadStartupEvent()
187 : mMon(nsAutoMonitor::NewMonitor("xpcom.threadstartup"))
188 , mInitialized(PR_FALSE) {
191 PRMonitor *mMon;
192 PRBool mInitialized;
195 //-----------------------------------------------------------------------------
197 struct nsThreadShutdownContext {
198 nsThread *joiningThread;
199 PRBool shutdownAck;
202 // This event is responsible for notifying nsThread::Shutdown that it is time
203 // to call PR_JoinThread.
204 class nsThreadShutdownAckEvent : public nsRunnable {
205 public:
206 nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx)
207 : mShutdownContext(ctx) {
209 NS_IMETHOD Run() {
210 mShutdownContext->shutdownAck = PR_TRUE;
211 return NS_OK;
213 private:
214 nsThreadShutdownContext *mShutdownContext;
217 // This event is responsible for setting mShutdownContext
218 class nsThreadShutdownEvent : public nsRunnable {
219 public:
220 nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx)
221 : mThread(thr), mShutdownContext(ctx) {
223 NS_IMETHOD Run() {
224 mThread->mShutdownContext = mShutdownContext;
225 return NS_OK;
227 private:
228 nsRefPtr<nsThread> mThread;
229 nsThreadShutdownContext *mShutdownContext;
232 //-----------------------------------------------------------------------------
234 /*static*/ void
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");
247 return;
249 event->Run(); // unblocks nsThread::Init
250 event = nsnull;
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.
261 while (PR_TRUE) {
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;
270 break;
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);
283 NS_RELEASE(self);
286 //-----------------------------------------------------------------------------
288 nsThread::nsThread()
289 : mLock(PR_NewLock())
290 , mEvents(&mEventsRoot)
291 , mPriority(PRIORITY_NORMAL)
292 , mThread(nsnull)
293 , mRunningEvent(0)
294 , mShutdownContext(nsnull)
295 , mShutdownRequired(PR_FALSE)
296 , mEventsAreDoomed(PR_FALSE)
300 nsThread::~nsThread()
302 if (mLock)
303 PR_DestroyLock(mLock);
306 nsresult
307 nsThread::Init()
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);
315 NS_ADDREF_THIS();
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);
323 if (!thr) {
324 NS_RELEASE_THIS();
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.
338 startup->Wait();
339 return NS_OK;
342 nsresult
343 nsThread::InitCurrentThread()
345 NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
347 mThread = PR_GetCurrentThread();
349 nsThreadManager::get()->RegisterCurrentThread(this);
350 return NS_OK;
353 nsresult
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();
367 if (obs)
368 obs->OnDispatchedEvent(this);
370 return NS_OK;
373 //-----------------------------------------------------------------------------
374 // nsIEventTarget
376 NS_IMETHODIMP
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);
393 if (!wrapper)
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...
397 if (NS_FAILED(rv))
398 return rv;
400 while (wrapper->IsPending())
401 NS_ProcessNextEvent(thread);
402 return rv;
405 NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
406 return PutEvent(event);
409 NS_IMETHODIMP
410 nsThread::IsOnCurrentThread(PRBool *result)
412 *result = (PR_GetCurrentThread() == mThread);
413 return NS_OK;
416 //-----------------------------------------------------------------------------
417 // nsIThread
419 NS_IMETHODIMP
420 nsThread::GetPRThread(PRThread **result)
422 *result = mThread;
423 return NS_OK;
426 NS_IMETHODIMP
427 nsThread::Shutdown()
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.
434 if (!mThread)
435 return NS_OK;
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);
454 if (!event)
455 return NS_ERROR_OUT_OF_MEMORY;
456 // XXXroc What if posting the event fails due to OOM?
457 PutEvent(event);
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);
470 mThread = nsnull;
471 return NS_OK;
474 NS_IMETHODIMP
475 nsThread::HasPendingEvents(PRBool *result)
477 NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
479 *result = mEvents->GetEvent(PR_FALSE, nsnull);
480 return NS_OK;
483 NS_IMETHODIMP
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(),
493 mRunningEvent);
495 nsCOMPtr<nsIThreadObserver> obs = mObserver;
496 if (obs)
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);
505 nsresult rv = NS_OK;
507 if (event) {
508 LOG(("THRD(%p) running [%p]\n", this, event.get()));
509 ++mRunningEvent;
510 event->Run();
511 --mRunningEvent;
512 } else if (mayWait) {
513 NS_ASSERTION(ShuttingDown(), "This should only happen when shutting down");
514 rv = NS_ERROR_UNEXPECTED;
517 if (obs)
518 obs->AfterProcessNextEvent(this, mRunningEvent);
520 if (notifyGlobalObserver && sGlobalObserver)
521 sGlobalObserver->AfterProcessNextEvent(this, mRunningEvent);
523 return rv;
526 //-----------------------------------------------------------------------------
527 // nsISupportsPriority
529 NS_IMETHODIMP
530 nsThread::GetPriority(PRInt32 *priority)
532 *priority = mPriority;
533 return NS_OK;
536 NS_IMETHODIMP
537 nsThread::SetPriority(PRInt32 priority)
539 NS_ENSURE_STATE(mThread);
541 // NSPR defines the following four thread priorities:
542 // PR_PRIORITY_LOW
543 // PR_PRIORITY_NORMAL
544 // PR_PRIORITY_HIGH
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;
557 } else {
558 pri = PR_PRIORITY_NORMAL;
560 PR_SetThreadPriority(mThread, pri);
562 return NS_OK;
565 NS_IMETHODIMP
566 nsThread::AdjustPriority(PRInt32 delta)
568 return SetPriority(mPriority + delta);
571 //-----------------------------------------------------------------------------
572 // nsIThreadInternal
574 NS_IMETHODIMP
575 nsThread::GetObserver(nsIThreadObserver **obs)
577 nsAutoLock lock(mLock);
578 NS_IF_ADDREF(*obs = mObserver);
579 return NS_OK;
582 NS_IMETHODIMP
583 nsThread::SetObserver(nsIThreadObserver *obs)
585 NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
587 nsAutoLock lock(mLock);
588 mObserver = obs;
589 return NS_OK;
592 NS_IMETHODIMP
593 nsThread::PushEventQueue(nsIThreadEventFilter *filter)
595 nsChainedEventQueue *queue = new nsChainedEventQueue(filter);
596 if (!queue || !queue->IsInitialized()) {
597 delete queue;
598 return NS_ERROR_OUT_OF_MEMORY;
601 nsAutoLock lock(mLock);
602 queue->mNext = mEvents;
603 mEvents = queue;
604 return NS_OK;
607 NS_IMETHODIMP
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);
622 delete queue;
624 return NS_OK;
627 PRBool
628 nsThread::nsChainedEventQueue::PutEvent(nsIRunnable *event)
630 PRBool val;
631 if (!mFilter || mFilter->AcceptEvent(event)) {
632 val = mQueue.PutEvent(event);
633 } else {
634 val = mNext->PutEvent(event);
636 return val;
639 //-----------------------------------------------------------------------------
641 NS_IMETHODIMP
642 nsThreadSyncDispatch::Run()
644 if (mSyncTask) {
645 mSyncTask->Run();
646 mSyncTask = nsnull;
647 // unblock the origin thread
648 mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
650 return NS_OK;
653 nsresult
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;
665 return NS_OK;