1 /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is worker threads.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2008
20 * the Initial Developer. All Rights Reserved.
23 * Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
24 * Ben Turner <bent.mozilla@gmail.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsDOMThreadService.h"
43 #include "nsIComponentManager.h"
44 #include "nsIConsoleService.h"
45 #include "nsIDocument.h"
46 #include "nsIDOMDocument.h"
47 #include "nsIEventTarget.h"
48 #include "nsIGenericFactory.h"
49 #include "nsIJSContextStack.h"
50 #include "nsIJSRuntimeService.h"
51 #include "nsIObserverService.h"
52 #include "nsIScriptError.h"
53 #include "nsIScriptGlobalObject.h"
54 #include "nsIServiceManager.h"
55 #include "nsISupportsPriority.h"
56 #include "nsIThreadPool.h"
57 #include "nsIXPConnect.h"
60 #include "nsAutoLock.h"
61 #include "nsAutoPtr.h"
62 #include "nsContentUtils.h"
64 #include "nsIClassInfoImpl.h"
65 #include "nsProxyRelease.h"
66 #include "nsThreadUtils.h"
68 #include "nsXPCOMCID.h"
69 #include "nsXPCOMCIDInternal.h"
74 #include "nsDOMWorkerPool.h"
75 #include "nsDOMWorkerSecurityManager.h"
76 #include "nsDOMWorkerTimeout.h"
79 PRLogModuleInfo
*gDOMThreadsLog
= nsnull
;
81 #define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
83 // The maximum number of threads in the internal thread pool
84 #define THREADPOOL_MAX_THREADS 3
86 PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS
>= 1);
88 // The maximum number of idle threads in the internal thread pool
89 #define THREADPOOL_IDLE_THREADS 3
91 PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS
>= THREADPOOL_IDLE_THREADS
);
93 // As we suspend threads for various reasons (navigating away from the page,
94 // loading scripts, etc.) we open another slot in the thread pool for another
95 // worker to use. We can't do this forever so we set an absolute cap on the
96 // number of threads we'll allow to prevent DOS attacks.
97 #define THREADPOOL_THREAD_CAP 20
99 PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP
>= THREADPOOL_MAX_THREADS
);
101 // The number of times our JS operation callback will be called before yielding
103 #define CALLBACK_YIELD_THRESHOLD 100
105 // A "bad" value for the NSPR TLS functions.
106 #define BAD_TLS_INDEX (PRUintn)-1
108 // Don't know why nsISupports.idl defines this out...
109 #define NS_FORWARD_NSISUPPORTS(_to) \
110 NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) { \
111 return _to QueryInterface(uuid, result); \
113 NS_IMETHOD_(nsrefcnt) AddRef(void) { return _to AddRef(); } \
114 NS_IMETHOD_(nsrefcnt) Release(void) { return _to Release(); }
116 // Easy access for static functions. No reference here.
117 static nsDOMThreadService
* gDOMThreadService
= nsnull
;
119 // These pointers actually carry references and must be released.
120 static nsIObserverService
* gObserverService
= nsnull
;
121 static nsIJSRuntimeService
* gJSRuntimeService
= nsnull
;
122 static nsIThreadJSContextStack
* gThreadJSContextStack
= nsnull
;
123 static nsIXPCSecurityManager
* gWorkerSecurityManager
= nsnull
;
125 PRUintn gJSContextIndex
= BAD_TLS_INDEX
;
128 * Simple class to automatically destroy a JSContext to make error handling
131 class JSAutoContextDestroyer
134 JSAutoContextDestroyer(JSContext
* aCx
)
137 ~JSAutoContextDestroyer() {
139 nsContentUtils::XPConnect()->ReleaseJSContext(mCx
, PR_TRUE
);
143 operator JSContext
*() {
147 JSContext
* forget() {
158 * This class is used as to post an error to the main thread. It logs the error
159 * to the console and calls the pool's onError callback.
161 class nsReportErrorRunnable
: public nsRunnable
164 nsReportErrorRunnable(nsIScriptError
* aError
, nsDOMWorkerThread
* aWorker
)
165 : mError(aError
), mWorker(aWorker
) { }
170 nsCOMPtr
<nsIConsoleService
> consoleService
=
171 do_GetService(NS_CONSOLESERVICE_CONTRACTID
, &rv
);
172 if (NS_SUCCEEDED(rv
)) {
173 consoleService
->LogMessage(mError
);
176 if (!mWorker
->IsCanceled()) {
178 nsAutoString message
;
179 mError
->GetErrorMessage(message
);
181 nsRefPtr
<nsDOMWorkerPool
> pool
= mWorker
->Pool();
183 LOG(("Posting error '%s' to pool [0x%p]",
184 NS_LossyConvertUTF16toASCII(message
).get(),
185 static_cast<void*>(pool
.get())));
187 pool
->HandleError(mError
, mWorker
);
193 // XXX Maybe this should be an nsIException...
194 nsCOMPtr
<nsIScriptError
> mError
;
196 // Have to carry a strong ref since this is used as a parameter to the
198 nsRefPtr
<nsDOMWorkerThread
> mWorker
;
202 * Need this to expose an nsIScriptError to content JS (implement nsIClassInfo
203 * with DOM_OBJECT flag set.
205 class nsDOMWorkerScriptError
: public nsIClassInfo
211 nsDOMWorkerScriptError(nsIScriptError
* aError
)
212 : mScriptError(this, aError
) { }
216 // Lame, nsIScriptError and nsIClassInfo both have 'readonly attribute
217 // unsigned long flags' so we have to use an inner class to do this the
219 class InnerScriptError
: public nsIScriptError
222 NS_FORWARD_NSISUPPORTS(mParent
->)
223 NS_FORWARD_NSISCRIPTERROR(mError
->)
224 NS_FORWARD_NSICONSOLEMESSAGE(mError
->)
226 InnerScriptError(nsDOMWorkerScriptError
* aParent
, nsIScriptError
* aError
)
227 : mParent(aParent
), mError(aError
) { }
230 nsDOMWorkerScriptError
* mParent
;
231 nsCOMPtr
<nsIScriptError
> mError
;
234 InnerScriptError mScriptError
;
237 NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerScriptError
)
238 NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerScriptError
)
240 // More hoops to jump through for the identical IDL methods
241 NS_INTERFACE_MAP_BEGIN(nsDOMWorkerScriptError
)
242 if (aIID
.Equals(NS_GET_IID(nsIScriptError
)) ||
243 aIID
.Equals(NS_GET_IID(nsIConsoleMessage
))) {
244 foundInterface
= static_cast<nsIConsoleMessage
*>(&mScriptError
);
247 NS_INTERFACE_MAP_ENTRY(nsIClassInfo
)
248 NS_INTERFACE_MAP_ENTRY(nsISupports
)
251 NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerScriptError
, nsIScriptError
,
254 NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerScriptError
)
257 * Used to post an expired timeout to the correct worker.
259 class nsDOMWorkerTimeoutRunnable
: public nsRunnable
262 nsDOMWorkerTimeoutRunnable(nsDOMWorkerTimeout
* aTimeout
)
263 : mTimeout(aTimeout
) { }
266 return mTimeout
->Run();
269 nsRefPtr
<nsDOMWorkerTimeout
> mTimeout
;
273 * This class exists to solve a particular problem: Calling Dispatch on a
274 * thread pool will always create a new thread to service the runnable as long
275 * as the thread limit has not been reached. Since our DOM workers can only be
276 * accessed by one thread at a time we could end up spawning a new thread that
277 * does nothing but wait initially. There is no way to control this behavior
278 * currently so we cheat by using a runnable that emulates a thread. The
279 * nsDOMThreadService's monitor protects the queue of events.
281 class nsDOMWorkerRunnable
: public nsRunnable
283 friend class nsDOMThreadService
;
286 nsDOMWorkerRunnable(nsDOMWorkerThread
* aWorker
)
287 : mWorker(aWorker
) { }
289 virtual ~nsDOMWorkerRunnable() {
290 nsCOMPtr
<nsIRunnable
> runnable
;
291 while ((runnable
= dont_AddRef((nsIRunnable
*)mRunnables
.PopFront()))) {
292 // Loop until all the runnables are dead.
295 // Only release mWorker on the main thread!
296 nsDOMWorkerThread
* worker
= nsnull
;
297 mWorker
.swap(worker
);
299 nsISupports
* supports
= NS_ISUPPORTS_CAST(nsIDOMWorkerThread
*, worker
);
300 NS_ASSERTION(supports
, "This should never be null!");
302 nsCOMPtr
<nsIThread
> mainThread(do_GetMainThread());
303 NS_ProxyRelease(mainThread
, supports
);
306 void PutRunnable(nsIRunnable
* aRunnable
) {
307 NS_ASSERTION(aRunnable
, "Null pointer!");
309 NS_ADDREF(aRunnable
);
311 // No need to enter the monitor because we should already be in it.
313 mRunnables
.Push(aRunnable
);
317 // This must have been set up by the thread service
318 NS_ASSERTION(gJSContextIndex
!= BAD_TLS_INDEX
, "No context index!");
320 // Make sure we have a JSContext to run everything on.
321 JSContext
* cx
= (JSContext
*)PR_GetThreadPrivate(gJSContextIndex
);
322 NS_ASSERTION(cx
, "nsDOMThreadService didn't give us a context!");
324 JS_SetContextPrivate(cx
, mWorker
);
326 // Tell the worker which context it will be using
327 if (mWorker
->SetGlobalForContext(cx
)) {
330 // Remove the global object from the context so that it might be garbage
332 JS_SetGlobalObject(cx
, NULL
);
333 JS_SetContextPrivate(cx
, NULL
);
336 // This is usually due to a parse error in the worker script...
337 JS_SetGlobalObject(cx
, NULL
);
339 nsAutoMonitor
mon(gDOMThreadService
->mMonitor
);
340 gDOMThreadService
->WorkerComplete(this);
350 JSContext
* cx
= (JSContext
*)PR_GetThreadPrivate(gJSContextIndex
);
351 NS_ASSERTION(cx
, "nsDOMThreadService didn't give us a context!");
354 nsCOMPtr
<nsIRunnable
> runnable
;
356 nsAutoMonitor
mon(gDOMThreadService
->mMonitor
);
358 runnable
= dont_AddRef((nsIRunnable
*)mRunnables
.PopFront());
360 if (!runnable
|| mWorker
->IsCanceled()) {
362 if (mWorker
->IsCanceled()) {
363 LOG(("Bailing out of run loop for canceled worker[0x%p]",
364 static_cast<void*>(mWorker
.get())));
367 gDOMThreadService
->WorkerComplete(this);
373 // Clear out any old cruft hanging around in the regexp statics.
374 JS_ClearRegExpStatics(cx
);
380 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "Runnable failed!");
384 // Set at construction
385 nsRefPtr
<nsDOMWorkerThread
> mWorker
;
387 // Protected by mMonitor
391 /*******************************************************************************
392 * JS environment function and callbacks
396 DOMWorkerOperationCallback(JSContext
* aCx
)
398 nsDOMWorkerThread
* worker
= (nsDOMWorkerThread
*)JS_GetContextPrivate(aCx
);
400 // Want a strong ref here to make sure that the monitor we wait on won't go
402 nsRefPtr
<nsDOMWorkerPool
> pool
;
404 PRBool wasSuspended
= PR_FALSE
;
405 PRBool extraThreadAllowed
= PR_FALSE
;
406 jsrefcount suspendDepth
= 0;
409 // Kill execution if we're canceled.
410 if (worker
->IsCanceled()) {
411 LOG(("Forcefully killing JS for worker [0x%p]",
412 static_cast<void*>(worker
)));
415 if (extraThreadAllowed
) {
416 gDOMThreadService
->ChangeThreadPoolMaxThreads(-1);
418 JS_ResumeRequest(aCx
, suspendDepth
);
421 // Kill exectuion of the currently running JS.
425 // Break out if we're not suspended.
426 if (!worker
->IsSuspended()) {
428 if (extraThreadAllowed
) {
429 gDOMThreadService
->ChangeThreadPoolMaxThreads(-1);
431 JS_ResumeRequest(aCx
, suspendDepth
);
437 // Make sure we can get the monitor we need to wait on. It's possible that
438 // the worker was canceled since we checked above.
439 if (worker
->IsCanceled()) {
440 NS_WARNING("Tried to suspend on a pool that has gone away");
444 pool
= worker
->Pool();
446 // Make sure to suspend our request while we block like this, otherwise we
447 // prevent GC for everyone.
448 suspendDepth
= JS_SuspendRequest(aCx
);
450 // Since we're going to block this thread we should open up a new thread
451 // in the thread pool for other workers. Must check the return value to
452 // make sure we don't decrement when we failed.
454 NS_SUCCEEDED(gDOMThreadService
->ChangeThreadPoolMaxThreads(1));
456 // Only do all this setup once.
457 wasSuspended
= PR_TRUE
;
460 nsAutoMonitor
mon(pool
->Monitor());
464 // Since only one thread can access a context at once we don't have to worry
465 // about atomically incrementing this counter
466 if (++worker
->mCallbackCount
>= CALLBACK_YIELD_THRESHOLD
) {
467 // Must call this so that GC can happen on the main thread!
468 JS_YieldRequest(aCx
);
470 // Start the counter over.
471 worker
->mCallbackCount
= 0;
474 // Continue execution.
479 DOMWorkerErrorReporter(JSContext
* aCx
,
480 const char* aMessage
,
481 JSErrorReport
* aReport
)
483 NS_ASSERTION(!NS_IsMainThread(), "Huh?!");
485 nsDOMWorkerThread
* worker
= (nsDOMWorkerThread
*)JS_GetContextPrivate(aCx
);
487 if (worker
->IsCanceled()) {
488 // We don't want to report errors from canceled workers. It's very likely
489 // that we only returned an error in the first place because the worker was
495 nsCOMPtr
<nsIScriptError
> errorObject
=
496 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
, &rv
);
497 NS_ENSURE_SUCCESS(rv
,);
499 const PRUnichar
* message
=
500 reinterpret_cast<const PRUnichar
*>(aReport
->ucmessage
);
502 nsAutoString filename
;
503 filename
.AssignWithConversion(aReport
->filename
);
505 const PRUnichar
* line
=
506 reinterpret_cast<const PRUnichar
*>(aReport
->uclinebuf
);
508 PRUint32 column
= aReport
->uctokenptr
- aReport
->uclinebuf
;
510 rv
= errorObject
->Init(message
, filename
.get(), line
, aReport
->lineno
,
511 column
, aReport
->flags
, "DOM Worker javascript");
512 NS_ENSURE_SUCCESS(rv
,);
514 nsRefPtr
<nsDOMWorkerScriptError
> domError
=
515 new nsDOMWorkerScriptError(errorObject
);
516 NS_ENSURE_TRUE(domError
,);
518 nsCOMPtr
<nsIScriptError
> scriptError(do_QueryInterface(domError
));
519 NS_ENSURE_TRUE(scriptError
,);
521 nsCOMPtr
<nsIThread
> mainThread(do_GetMainThread());
522 NS_ENSURE_TRUE(mainThread
,);
524 nsCOMPtr
<nsIRunnable
> runnable
=
525 new nsReportErrorRunnable(scriptError
, worker
);
526 NS_ENSURE_TRUE(runnable
,);
528 rv
= mainThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
529 NS_ENSURE_SUCCESS(rv
,);
532 /*******************************************************************************
536 nsDOMThreadService::nsDOMThreadService()
539 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
541 if (!gDOMThreadsLog
) {
542 gDOMThreadsLog
= PR_NewLogModule("nsDOMThreads");
545 LOG(("Initializing DOM Thread service"));
548 nsDOMThreadService::~nsDOMThreadService()
550 LOG(("DOM Thread service destroyed"));
552 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
557 nsAutoMonitor::DestroyMonitor(mMonitor
);
561 NS_IMPL_THREADSAFE_ISUPPORTS4(nsDOMThreadService
, nsIEventTarget
,
563 nsIThreadPoolListener
,
567 nsDOMThreadService::Init()
569 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
570 NS_ASSERTION(!gDOMThreadService
, "Only one instance should ever be created!");
573 nsCOMPtr
<nsIObserverService
> obs
=
574 do_GetService(NS_OBSERVERSERVICE_CONTRACTID
, &rv
);
575 NS_ENSURE_SUCCESS(rv
, rv
);
577 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, PR_FALSE
);
578 NS_ENSURE_SUCCESS(rv
, rv
);
580 obs
.forget(&gObserverService
);
582 mThreadPool
= do_CreateInstance(NS_THREADPOOL_CONTRACTID
, &rv
);
583 NS_ENSURE_SUCCESS(rv
, rv
);
585 rv
= mThreadPool
->SetListener(this);
586 NS_ENSURE_SUCCESS(rv
, rv
);
588 rv
= mThreadPool
->SetThreadLimit(THREADPOOL_MAX_THREADS
);
589 NS_ENSURE_SUCCESS(rv
, rv
);
591 rv
= mThreadPool
->SetIdleThreadLimit(THREADPOOL_IDLE_THREADS
);
592 NS_ENSURE_SUCCESS(rv
, rv
);
594 mMonitor
= nsAutoMonitor::NewMonitor("nsDOMThreadService::mMonitor");
595 NS_ENSURE_TRUE(mMonitor
, NS_ERROR_OUT_OF_MEMORY
);
597 PRBool success
= mWorkersInProgress
.Init();
598 NS_ENSURE_TRUE(success
, NS_ERROR_OUT_OF_MEMORY
);
600 nsCOMPtr
<nsIJSRuntimeService
>
601 runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
602 NS_ENSURE_TRUE(runtimeSvc
, NS_ERROR_FAILURE
);
603 runtimeSvc
.forget(&gJSRuntimeService
);
605 nsCOMPtr
<nsIThreadJSContextStack
>
606 contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1"));
607 NS_ENSURE_TRUE(contextStack
, NS_ERROR_FAILURE
);
608 contextStack
.forget(&gThreadJSContextStack
);
610 nsCOMPtr
<nsIXPCSecurityManager
> secMan(new nsDOMWorkerSecurityManager());
611 NS_ENSURE_TRUE(secMan
, NS_ERROR_OUT_OF_MEMORY
);
612 secMan
.forget(&gWorkerSecurityManager
);
614 if (gJSContextIndex
== BAD_TLS_INDEX
&&
615 PR_NewThreadPrivateIndex(&gJSContextIndex
, NULL
) != PR_SUCCESS
) {
616 NS_ERROR("PR_NewThreadPrivateIndex failed!");
617 gJSContextIndex
= BAD_TLS_INDEX
;
618 return NS_ERROR_FAILURE
;
625 already_AddRefed
<nsIDOMThreadService
>
626 nsDOMThreadService::GetOrInitService()
628 if (!gDOMThreadService
) {
629 nsRefPtr
<nsDOMThreadService
> service
= new nsDOMThreadService();
630 NS_ENSURE_TRUE(service
, nsnull
);
632 nsresult rv
= service
->Init();
633 NS_ENSURE_SUCCESS(rv
, nsnull
);
635 service
.swap(gDOMThreadService
);
638 nsCOMPtr
<nsIDOMThreadService
> service(gDOMThreadService
);
639 return service
.forget();
644 nsDOMThreadService::get()
646 return gDOMThreadService
;
651 nsDOMThreadService::Shutdown()
653 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
654 NS_IF_RELEASE(gDOMThreadService
);
658 nsDOMThreadService::Cleanup()
660 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
662 // This will either be called at 'xpcom-shutdown' or earlier if the call to
663 // Init fails somehow. We can therefore assume that all services will still
664 // be available here.
666 if (gObserverService
) {
667 gObserverService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
668 NS_RELEASE(gObserverService
);
671 // The thread pool holds a circular reference to this service through its
672 // listener. We must shut down the thread pool manually to break this cycle.
674 mThreadPool
->Shutdown();
675 mThreadPool
= nsnull
;
678 // Need to force a GC so that all of our workers get cleaned up.
679 if (gThreadJSContextStack
) {
680 JSContext
* safeContext
;
681 if (NS_SUCCEEDED(gThreadJSContextStack
->GetSafeJSContext(&safeContext
))) {
684 NS_RELEASE(gThreadJSContextStack
);
687 // These must be released after the thread pool is shut down.
688 NS_IF_RELEASE(gJSRuntimeService
);
689 NS_IF_RELEASE(gWorkerSecurityManager
);
693 nsDOMThreadService::Dispatch(nsDOMWorkerThread
* aWorker
,
694 nsIRunnable
* aRunnable
)
696 NS_ASSERTION(aWorker
, "Null pointer!");
697 NS_ASSERTION(aRunnable
, "Null pointer!");
699 NS_ASSERTION(mThreadPool
, "Dispatch called after 'xpcom-shutdown'!");
701 if (aWorker
->IsCanceled()) {
702 LOG(("Will not dispatch runnable [0x%p] for canceled worker [0x%p]",
703 static_cast<void*>(aRunnable
), static_cast<void*>(aWorker
)));
704 return NS_ERROR_NOT_AVAILABLE
;
707 nsRefPtr
<nsDOMWorkerRunnable
> workerRunnable
;
709 nsAutoMonitor
mon(mMonitor
);
711 if (mWorkersInProgress
.Get(aWorker
, getter_AddRefs(workerRunnable
))) {
712 workerRunnable
->PutRunnable(aRunnable
);
716 workerRunnable
= new nsDOMWorkerRunnable(aWorker
);
717 NS_ENSURE_TRUE(workerRunnable
, NS_ERROR_OUT_OF_MEMORY
);
719 workerRunnable
->PutRunnable(aRunnable
);
721 PRBool success
= mWorkersInProgress
.Put(aWorker
, workerRunnable
);
722 NS_ENSURE_TRUE(success
, NS_ERROR_OUT_OF_MEMORY
);
725 nsresult rv
= mThreadPool
->Dispatch(workerRunnable
, NS_DISPATCH_NORMAL
);
727 // XXX This is a mess and it could probably be removed once we have an
728 // infallible malloc implementation.
730 NS_WARNING("Failed to dispatch runnable to thread pool!");
732 nsAutoMonitor
mon(mMonitor
);
734 // We exited the monitor after inserting the runnable into the table so make
735 // sure we're removing the right one!
736 nsRefPtr
<nsDOMWorkerRunnable
> tableRunnable
;
737 if (mWorkersInProgress
.Get(aWorker
, getter_AddRefs(tableRunnable
)) &&
738 workerRunnable
== tableRunnable
) {
739 mWorkersInProgress
.Remove(aWorker
);
741 // And don't forget to tell anyone who's waiting.
752 nsDOMThreadService::WorkerComplete(nsDOMWorkerRunnable
* aRunnable
)
755 // No need to be in the monitor here because we should already be in it.
758 nsRefPtr
<nsDOMWorkerThread
>& debugWorker
= aRunnable
->mWorker
;
760 nsRefPtr
<nsDOMWorkerRunnable
> runnable
;
761 NS_ASSERTION(mWorkersInProgress
.Get(debugWorker
, getter_AddRefs(runnable
)) &&
762 runnable
== aRunnable
,
763 "Removing a worker that isn't in our hashtable?!");
766 mWorkersInProgress
.Remove(aRunnable
->mWorker
);
770 nsDOMThreadService::WaitForCanceledWorker(nsDOMWorkerThread
* aWorker
)
772 NS_ASSERTION(aWorker
->IsCanceled(),
773 "Waiting on a worker that isn't canceled!");
775 nsAutoMonitor
mon(mMonitor
);
777 while (mWorkersInProgress
.Get(aWorker
, nsnull
)) {
784 nsDOMThreadService::CreateJSContext()
787 gJSRuntimeService
->GetRuntime(&rt
);
788 NS_ENSURE_TRUE(rt
, nsnull
);
790 JSAutoContextDestroyer
cx(JS_NewContext(rt
, 8192));
791 NS_ENSURE_TRUE(cx
, nsnull
);
793 JS_SetErrorReporter(cx
, DOMWorkerErrorReporter
);
795 JS_SetOperationCallback(cx
, DOMWorkerOperationCallback
,
796 100 * JS_OPERATION_WEIGHT_BASE
);
798 static JSSecurityCallbacks securityCallbacks
= {
799 nsDOMWorkerSecurityManager::JSCheckAccess
,
800 nsDOMWorkerSecurityManager::JSTranscodePrincipals
,
801 nsDOMWorkerSecurityManager::JSFindPrincipal
804 JS_SetContextSecurityCallbacks(cx
, &securityCallbacks
);
806 nsresult rv
= nsContentUtils::XPConnect()->
807 SetSecurityManagerForJSContext(cx
, gWorkerSecurityManager
, 0);
808 NS_ENSURE_SUCCESS(rv
, nsnull
);
813 #define LOOP_OVER_POOLS(_func, _args) \
815 PRUint32 poolCount = mPools.Length(); \
816 for (PRUint32 i = 0; i < poolCount; i++) { \
817 mPools[i]-> _func _args ; \
822 nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject
* aGlobalObject
)
824 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
825 LOOP_OVER_POOLS(CancelWorkersForGlobal
, (aGlobalObject
));
829 nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject
* aGlobalObject
)
831 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
832 LOOP_OVER_POOLS(SuspendWorkersForGlobal
, (aGlobalObject
));
836 nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject
* aGlobalObject
)
838 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
839 LOOP_OVER_POOLS(ResumeWorkersForGlobal
, (aGlobalObject
));
843 nsDOMThreadService::NoteDyingPool(nsDOMWorkerPool
* aPool
)
845 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
847 NS_ASSERTION(mPools
.Contains(aPool
), "aPool should be in the array!");
848 mPools
.RemoveElement(aPool
);
852 nsDOMThreadService::TimeoutReady(nsDOMWorkerTimeout
* aTimeout
)
854 nsRefPtr
<nsDOMWorkerTimeoutRunnable
> runnable
=
855 new nsDOMWorkerTimeoutRunnable(aTimeout
);
856 NS_ENSURE_TRUE(runnable
,);
858 Dispatch(aTimeout
->GetWorker(), runnable
);
862 nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta
)
864 NS_ENSURE_ARG(aDelta
== 1 || aDelta
== -1);
866 PRUint32 currentThreadCount
;
867 nsresult rv
= mThreadPool
->GetThreadLimit(¤tThreadCount
);
868 NS_ENSURE_SUCCESS(rv
, rv
);
870 PRInt32 newThreadCount
= (PRInt32
)currentThreadCount
+ (PRInt32
)aDelta
;
871 NS_ASSERTION(newThreadCount
>= THREADPOOL_MAX_THREADS
,
872 "Can't go below initial thread count!");
874 if (newThreadCount
> THREADPOOL_THREAD_CAP
) {
875 NS_WARNING("Thread pool cap reached!");
876 return NS_ERROR_FAILURE
;
879 rv
= mThreadPool
->SetThreadLimit((PRUint32
)newThreadCount
);
880 NS_ENSURE_SUCCESS(rv
, rv
);
886 nsDOMThreadService::JSRuntimeService()
888 return gJSRuntimeService
;
891 nsIThreadJSContextStack
*
892 nsDOMThreadService::ThreadJSContextStack()
894 return gThreadJSContextStack
;
897 nsIXPCSecurityManager
*
898 nsDOMThreadService::WorkerSecurityManager()
900 return gWorkerSecurityManager
;
907 nsDOMThreadService::Dispatch(nsIRunnable
* aEvent
,
910 NS_ENSURE_ARG_POINTER(aEvent
);
911 NS_ENSURE_FALSE(aFlags
& NS_DISPATCH_SYNC
, NS_ERROR_NOT_IMPLEMENTED
);
913 // This should only ever be called by the timer code! We run the event right
914 // now, but all that does is queue the real event for the proper worker.
925 nsDOMThreadService::IsOnCurrentThread(PRBool
* _retval
)
927 NS_NOTREACHED("No one should call this!");
928 return NS_ERROR_NOT_IMPLEMENTED
;
935 nsDOMThreadService::Observe(nsISupports
* aSubject
,
937 const PRUnichar
* aData
)
939 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
941 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
946 NS_NOTREACHED("Unknown observer topic!");
951 * See nsIThreadPoolListener
954 nsDOMThreadService::OnThreadCreated()
956 LOG(("Thread created"));
958 nsIThread
* current
= NS_GetCurrentThread();
960 // We want our worker threads to always have a lower priority than the main
961 // thread. NSPR docs say that this isn't incredibly reliable across all
962 // platforms but we hope for the best.
963 nsCOMPtr
<nsISupportsPriority
> priority(do_QueryInterface(current
));
964 NS_ENSURE_TRUE(priority
, NS_ERROR_FAILURE
);
966 nsresult rv
= priority
->SetPriority(nsISupportsPriority::PRIORITY_LOWEST
);
967 NS_ENSURE_SUCCESS(rv
, rv
);
969 NS_ASSERTION(gJSContextIndex
!= BAD_TLS_INDEX
, "No context index!");
971 // Set the context up for the worker.
972 JSContext
* cx
= (JSContext
*)PR_GetThreadPrivate(gJSContextIndex
);
974 cx
= nsDOMThreadService::CreateJSContext();
975 NS_ENSURE_TRUE(cx
, NS_ERROR_FAILURE
);
977 PRStatus status
= PR_SetThreadPrivate(gJSContextIndex
, cx
);
978 if (status
!= PR_SUCCESS
) {
979 NS_WARNING("Failed to set context on thread!");
980 nsContentUtils::XPConnect()->ReleaseJSContext(cx
, PR_TRUE
);
981 return NS_ERROR_FAILURE
;
985 // Make sure that XPConnect knows about this context.
986 gThreadJSContextStack
->Push(cx
);
987 gThreadJSContextStack
->SetSafeJSContext(cx
);
993 nsDOMThreadService::OnThreadShuttingDown()
995 LOG(("Thread shutting down"));
997 NS_ASSERTION(gJSContextIndex
!= BAD_TLS_INDEX
, "No context index!");
999 JSContext
* cx
= (JSContext
*)PR_GetThreadPrivate(gJSContextIndex
);
1000 NS_WARN_IF_FALSE(cx
, "Thread died with no context?");
1002 JSContext
* pushedCx
;
1003 gThreadJSContextStack
->Pop(&pushedCx
);
1004 NS_ASSERTION(pushedCx
== cx
, "Popped the wrong context!");
1006 gThreadJSContextStack
->SetSafeJSContext(nsnull
);
1008 nsContentUtils::XPConnect()->ReleaseJSContext(cx
, PR_TRUE
);
1015 * See nsIDOMThreadService
1018 nsDOMThreadService::CreatePool(nsIDOMWorkerPool
** _retval
)
1020 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1022 NS_ENSURE_TRUE(mThreadPool
, NS_ERROR_ILLEGAL_DURING_SHUTDOWN
);
1024 nsIDOMDocument
* domDocument
= nsContentUtils::GetDocumentFromCaller();
1025 NS_ENSURE_TRUE(domDocument
, NS_ERROR_UNEXPECTED
);
1027 nsCOMPtr
<nsIDocument
> callingDocument(do_QueryInterface(domDocument
));
1028 NS_ENSURE_TRUE(callingDocument
, NS_ERROR_NO_INTERFACE
);
1030 nsRefPtr
<nsDOMWorkerPool
> pool(new nsDOMWorkerPool(callingDocument
));
1031 NS_ENSURE_TRUE(pool
, NS_ERROR_OUT_OF_MEMORY
);
1033 nsresult rv
= pool
->Init();
1034 NS_ENSURE_SUCCESS(rv
, rv
);
1036 NS_ASSERTION(!mPools
.Contains(pool
), "Um?!");
1037 mPools
.AppendElement(pool
);
1039 NS_ADDREF(*_retval
= pool
);