☠☠ backed out by cc0753a23f8b ☠ ☠ | |
author | Ben Turner <bent.mozilla@gmail.com> |
Thu, 08 Sep 2011 17:03:03 -0700 | |
changeset 76796 | 58d026601240cec8a26acc78a7cfdb6dc0bb55ae |
parent 76795 | 78e81382db7f7c33f116ffff0bd521bca2513ece |
child 76797 | c5a9a439d72ca0811d977e0c7103b96ba14c8289 |
push id | 21141 |
push user | eakhgari@mozilla.com |
push date | Fri, 09 Sep 2011 14:06:30 +0000 |
treeherder | mozilla-central@694520af9b18 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mrbkap |
bugs | 679551 |
milestone | 9.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/workers/ChromeWorkerScope.cpp +++ b/dom/workers/ChromeWorkerScope.cpp @@ -40,16 +40,20 @@ #include "jsapi.h" #include "jscntxt.h" #include "nsXPCOM.h" #include "nsNativeCharsetUtils.h" #include "nsStringGlue.h" +#include "WorkerPrivate.h" + +#define CTYPES_STR "ctypes" + USING_WORKERS_NAMESPACE namespace { #ifdef BUILD_CTYPES char* UnicodeToNative(JSContext* aCx, const jschar* aSource, size_t aSourceLen) { @@ -69,34 +73,75 @@ UnicodeToNative(JSContext* aCx, const js memcpy(result, native.get(), native.Length()); result[native.Length()] = 0; return result; } JSCTypesCallbacks gCTypesCallbacks = { UnicodeToNative }; + +JSBool +CTypesLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp) +{ + NS_ASSERTION(JS_GetGlobalObject(aCx) == aObj, "Not a global object!"); + NS_ASSERTION(JSID_IS_STRING(aId), "Bad id!"); + NS_ASSERTION(JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), CTYPES_STR), + "Bad id!"); + + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + NS_ASSERTION(worker->IsChromeWorker(), "This should always be true!"); + + if (!worker->DisableMemoryReporter()) { + return false; + } + + jsval ctypes; + return JS_DeletePropertyById(aCx, aObj, aId) && + JS_InitCTypesClass(aCx, aObj) && + JS_GetPropertyById(aCx, aObj, aId, &ctypes) && + JS_SetCTypesCallbacks(aCx, JSVAL_TO_OBJECT(ctypes), + &gCTypesCallbacks) && + JS_GetPropertyById(aCx, aObj, aId, aVp); +} #endif +inline bool +DefineCTypesLazyGetter(JSContext* aCx, JSObject* aGlobal) +{ +#ifdef BUILD_CTYPES + { + JSString* ctypesStr = JS_InternString(aCx, CTYPES_STR); + if (!ctypesStr) { + return false; + } + + jsid ctypesId = INTERNED_STRING_TO_JSID(aCx, ctypesStr); + + // We use a lazy getter here to let us unregister the blocking memory + // reporter since ctypes can easily block the worker thread and we can + // deadlock. Remove once bug 673323 is fixed. + if (!JS_DefinePropertyById(aCx, aGlobal, ctypesId, JSVAL_VOID, + CTypesLazyGetter, NULL, 0)) { + return false; + } + } +#endif + + return true; +} + } // anonymous namespace BEGIN_WORKERS_NAMESPACE namespace chromeworker { bool DefineChromeWorkerFunctions(JSContext* aCx, JSObject* aGlobal) { -#ifdef BUILD_CTYPES - jsval ctypes; - if (!JS_InitCTypesClass(aCx, aGlobal) || - !JS_GetProperty(aCx, aGlobal, "ctypes", &ctypes) || - !JS_SetCTypesCallbacks(aCx, JSVAL_TO_OBJECT(ctypes), &gCTypesCallbacks)) { - return false; - } -#endif - - return true; + // Currently ctypes is the only special property given to ChromeWorkers. + return DefineCTypesLazyGetter(aCx, aGlobal); } } // namespace chromeworker END_WORKERS_NAMESPACE
--- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -41,23 +41,21 @@ #include "nsIDOMChromeWindow.h" #include "nsIDocument.h" #include "nsIEffectiveTLDService.h" #include "nsIObserverService.h" #include "nsIPlatformCharset.h" #include "nsIPrincipal.h" #include "nsIJSContextStack.h" -#include "nsIMemoryReporter.h" #include "nsIScriptSecurityManager.h" #include "nsISupportsPriority.h" #include "nsITimer.h" #include "nsPIDOMWindow.h" -#include "jsprf.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsGlobalWindow.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" @@ -282,71 +280,16 @@ CreateJSContextForWorker(WorkerPrivate* if (aWorkerPrivate->IsChromeWorker()) { JS_SetVersion(workerCx, JSVERSION_LATEST); } return workerCx; } -class WorkerMemoryReporter : public nsIMemoryMultiReporter -{ - WorkerPrivate* mWorkerPrivate; - nsCString mPathPrefix; - -public: - NS_DECL_ISUPPORTS - - WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate) - : mWorkerPrivate(aWorkerPrivate) - { - aWorkerPrivate->AssertIsOnWorkerThread(); - - nsCString escapedDomain(aWorkerPrivate->Domain()); - escapedDomain.ReplaceChar('/', '\\'); - - NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL()); - escapedURL.ReplaceChar('/', '\\'); - - // 64bit address plus '0x' plus null terminator. - char address[21]; - JSUint32 addressSize = - JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate); - if (addressSize == JSUint32(-1)) { - NS_WARNING("JS_snprintf failed!"); - address[0] = '\0'; - addressSize = 0; - } - - mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") + - escapedDomain + NS_LITERAL_CSTRING(")/worker(") + - escapedURL + NS_LITERAL_CSTRING(", ") + - nsDependentCString(address, addressSize) + - NS_LITERAL_CSTRING(")/"); - } - - NS_IMETHOD - CollectReports(nsIMemoryMultiReporterCallback* aCallback, - nsISupports* aClosure) - { - AssertIsOnMainThread(); - - IterateData data; - if (!mWorkerPrivate->BlockAndCollectRuntimeStats(&data)) { - return NS_ERROR_FAILURE; - } - - ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure); - - return NS_OK; - } -}; - -NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter) - class WorkerThreadRunnable : public nsRunnable { WorkerPrivate* mWorkerPrivate; public: WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) { @@ -363,35 +306,21 @@ public: JSContext* cx = CreateJSContextForWorker(workerPrivate); if (!cx) { // XXX need to fire an error at parent. NS_ERROR("Failed to create runtime and context!"); return NS_ERROR_FAILURE; } - nsRefPtr<WorkerMemoryReporter> reporter = - new WorkerMemoryReporter(workerPrivate); - if (NS_FAILED(NS_RegisterMemoryMultiReporter(reporter))) { - NS_WARNING("Failed to register memory reporter!"); - reporter = nsnull; - } - { JSAutoRequest ar(cx); workerPrivate->DoRunLoop(cx); } - if (reporter) { - if (NS_FAILED(NS_UnregisterMemoryMultiReporter(reporter))) { - NS_WARNING("Failed to unregister memory reporter!"); - } - reporter = nsnull; - } - JSRuntime* rt = JS_GetRuntime(cx); // XXX Bug 666963 - CTypes can create another JSContext for use with // closures, and then it holds that context in a reserved slot on the CType // prototype object. We have to destroy that context before we can destroy // the runtime, and we also have to make sure that it isn't the last context // to be destroyed (otherwise it will assert). To accomplish this we create // an unused dummy context, destroy our real context, and then destroy the
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -40,28 +40,30 @@ #include "WorkerPrivate.h" #include "nsIClassInfo.h" #include "nsIConsoleService.h" #include "nsIDOMFile.h" #include "nsIDocument.h" #include "nsIEffectiveTLDService.h" #include "nsIJSContextStack.h" +#include "nsIMemoryReporter.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptSecurityManager.h" #include "nsPIDOMWindow.h" #include "nsITextToSubURI.h" #include "nsITimer.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsIXPConnect.h" #include "jscntxt.h" #include "jsdbgapi.h" +#include "jsprf.h" #include "nsAlgorithm.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsDOMJSUtils.h" #include "nsGUIEvent.h" #include "nsJSEnvironment.h" #include "nsJSUtils.h" #include "nsNetUtil.h" @@ -135,16 +137,97 @@ SwapToISupportsArray(SmartPtr<T>& aSrc, T* raw = nsnull; aSrc.swap(raw); nsISupports* rawSupports = static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw); dest->swap(rawSupports); } +class WorkerMemoryReporter : public nsIMemoryMultiReporter +{ + WorkerPrivate* mWorkerPrivate; + nsCString mAddressString; + nsCString mPathPrefix; + +public: + NS_DECL_ISUPPORTS + + WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate) + : mWorkerPrivate(aWorkerPrivate) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + + nsCString escapedDomain(aWorkerPrivate->Domain()); + escapedDomain.ReplaceChar('/', '\\'); + + NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL()); + escapedURL.ReplaceChar('/', '\\'); + + { + // 64bit address plus '0x' plus null terminator. + char address[21]; + JSUint32 addressSize = + JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate); + if (addressSize != JSUint32(-1)) { + mAddressString.Assign(address, addressSize); + } + else { + NS_WARNING("JS_snprintf failed!"); + mAddressString.AssignLiteral("<unknown address>"); + } + } + + mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") + + escapedDomain + NS_LITERAL_CSTRING(")/worker(") + + escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString + + NS_LITERAL_CSTRING(")/"); + } + + NS_IMETHOD + CollectReports(nsIMemoryMultiReporterCallback* aCallback, + nsISupports* aClosure) + { + AssertIsOnMainThread(); + + IterateData data; + + if (mWorkerPrivate) { + bool disabled; + if (!mWorkerPrivate->BlockAndCollectRuntimeStats(&data, &disabled)) { + return NS_ERROR_FAILURE; + } + + // Don't ever try to talk to the worker again. + if (disabled) { +#ifdef DEBUG + { + nsCAutoString message("Unable to report memory for "); + if (mWorkerPrivate->IsChromeWorker()) { + message.AppendLiteral("Chrome"); + } + message += NS_LITERAL_CSTRING("Worker (") + mAddressString + + NS_LITERAL_CSTRING(")! It is either using ctypes or is in " + "the process of being destroyed"); + NS_WARNING(message.get()); + } +#endif + mWorkerPrivate = nsnull; + } + } + + // Always report, even if we're disabled, so that we at least get an entry + // in about::memory. + ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure); + return NS_OK; + } +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter) + struct WorkerStructuredCloneCallbacks { static JSObject* Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32 aTag, uint32 aData, void* aClosure) { // See if object is a nsIDOMFile pointer. if (aTag == DOMWORKER_SCTAG_FILE) { @@ -1277,29 +1360,29 @@ public: }; #endif class CollectRuntimeStatsRunnable : public WorkerControlRunnable { typedef mozilla::Mutex Mutex; typedef mozilla::CondVar CondVar; - Mutex* mMutex; - CondVar* mCondVar; - volatile bool* mDoneFlag; + Mutex mMutex; + CondVar mCondVar; + volatile bool mDone; IterateData* mData; - volatile bool* mSucceeded; + bool* mSucceeded; public: - CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, Mutex* aMutex, - CondVar* aCondVar, volatile bool* aDoneFlag, - IterateData* aData, volatile bool* aSucceeded) + CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, IterateData* aData, + bool* aSucceeded) : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), - mMutex(aMutex), mCondVar(aCondVar), mDoneFlag(aDoneFlag), mData(aData), - mSucceeded(aSucceeded) + mMutex("CollectRuntimeStatsRunnable::mMutex"), + mCondVar(mMutex, "CollectRuntimeStatsRunnable::mCondVar"), mDone(false), + mData(aData), mSucceeded(aSucceeded) { } bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { AssertIsOnMainThread(); return true; } @@ -1307,29 +1390,46 @@ public: void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aDispatchResult) { AssertIsOnMainThread(); } bool + DispatchInternal() + { + AssertIsOnMainThread(); + + if (!WorkerControlRunnable::DispatchInternal()) { + NS_WARNING("Failed to dispatch runnable!"); + return false; + } + + { + MutexAutoLock lock(mMutex); + while (!mDone) { + mCondVar.Wait(); + } + } + + return true; + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { JSAutoSuspendRequest asr(aCx); *mSucceeded = CollectCompartmentStatsForRuntime(JS_GetRuntime(aCx), mData); { - MutexAutoLock lock(*mMutex); - - NS_ASSERTION(!*mDoneFlag, "Should be false!"); - - *mDoneFlag = true; - mCondVar->Notify(); + MutexAutoLock lock(mMutex); + mDone = true; + mCondVar.Notify(); } return true; } }; } /* anonymous namespace */ @@ -2096,17 +2196,18 @@ WorkerPrivate::WorkerPrivate(JSContext* nsCOMPtr<nsIDocument>& aDocument) : WorkerPrivateParent<WorkerPrivate>(aCx, aObject, aParent, aParentJSContext, aScriptURL, aIsChromeWorker, aDomain, aWindow, aParentScriptContext, aBaseURI, aPrincipal, aDocument), mJSContext(nsnull), mErrorHandlerRecursionCount(0), mNextTimeoutId(1), mStatus(Pending), mSuspended(false), mTimerRunning(false), mRunningExpiredTimeouts(false), mCloseHandlerStarted(false), - mCloseHandlerFinished(false) + mCloseHandlerFinished(false), mMemoryReporterRunning(false), + mMemoryReporterDisabled(false) { MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate); } WorkerPrivate::~WorkerPrivate() { MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate); } @@ -2303,16 +2404,23 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) { MutexAutoLock lock(mMutex); mJSContext = aCx; NS_ASSERTION(mStatus == Pending, "Huh?!"); mStatus = Running; } + mMemoryReporter = new WorkerMemoryReporter(this); + + if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) { + NS_WARNING("Failed to register memory reporter!"); + mMemoryReporter = nsnull; + } + for (;;) { Status currentStatus; nsIRunnable* event; { MutexAutoLock lock(mMutex); while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) { mCondVar.Wait(); @@ -2353,16 +2461,27 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) NS_ASSERTION(currentStatus == Killing, "Should have changed status!"); #else currentStatus = Killing; #endif } // If we're supposed to die then we should exit the loop. if (currentStatus == Killing) { + // Call this before unregistering the reporter as we may be racing with + // the main thread. + DisableMemoryReporter(); + + if (mMemoryReporter) { + if (NS_FAILED(NS_UnregisterMemoryMultiReporter(mMemoryReporter))) { + NS_WARNING("Failed to unregister memory reporter!"); + } + mMemoryReporter = nsnull; + } + StopAcceptingEvents(); return; } } } NS_NOTREACHED("Shouldn't get here!"); } @@ -2371,31 +2490,17 @@ bool WorkerPrivate::OperationCallback(JSContext* aCx) { AssertIsOnWorkerThread(); bool mayContinue = true; for (;;) { // Run all control events now. - for (;;) { - nsIRunnable* event; - { - MutexAutoLock lock(mMutex); - if (!mControlQueue.Pop(event)) { - break; - } - } - - if (NS_FAILED(event->Run())) { - mayContinue = false; - } - - NS_RELEASE(event); - } + mayContinue = ProcessAllControlRunnables(); if (!mayContinue || !mSuspended) { break; } // Clean up before suspending. JS_FlushCaches(aCx); JS_GC(aCx); @@ -2452,46 +2557,97 @@ WorkerPrivate::ScheduleDeletion(bool aWa new TopLevelWorkerFinishedRunnable(this, currentThread); if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { NS_WARNING("Failed to dispatch runnable!"); } } } bool -WorkerPrivate::BlockAndCollectRuntimeStats(IterateData* aData) +WorkerPrivate::BlockAndCollectRuntimeStats(IterateData* aData, bool* aDisabled) { AssertIsOnMainThread(); - mMutex.AssertNotCurrentThreadOwns(); NS_ASSERTION(aData, "Null data!"); - mozilla::Mutex mutex("BlockAndCollectRuntimeStats mutex"); - mozilla::CondVar condvar(mutex, "BlockAndCollectRuntimeStats condvar"); - volatile bool doneFlag = false; - volatile bool succeeded = false; + { + MutexAutoLock lock(mMutex); + + if (mMemoryReporterDisabled) { + *aDisabled = true; + return true; + } + + *aDisabled = false; + mMemoryReporterRunning = true; + } + + bool succeeded; nsRefPtr<CollectRuntimeStatsRunnable> runnable = - new CollectRuntimeStatsRunnable(this, &mutex, &condvar, &doneFlag, aData, - &succeeded); + new CollectRuntimeStatsRunnable(this, aData, &succeeded); if (!runnable->Dispatch(nsnull)) { NS_WARNING("Failed to dispatch runnable!"); - return false; + succeeded = false; } { - MutexAutoLock lock(mutex); - while (!doneFlag) { - condvar.Wait(); - } + MutexAutoLock lock(mMutex); + mMemoryReporterRunning = false; } return succeeded; } bool +WorkerPrivate::DisableMemoryReporter() +{ + AssertIsOnWorkerThread(); + + bool result = true; + + { + MutexAutoLock lock(mMutex); + + mMemoryReporterDisabled = true; + + while (mMemoryReporterRunning) { + MutexAutoUnlock unlock(mMutex); + result = ProcessAllControlRunnables() && result; + } + } + + return result; +} + +bool +WorkerPrivate::ProcessAllControlRunnables() +{ + AssertIsOnWorkerThread(); + + bool result = true; + + for (;;) { + nsIRunnable* event; + { + MutexAutoLock lock(mMutex); + if (!mControlQueue.Pop(event)) { + break; + } + } + + if (NS_FAILED(event->Run())) { + result = false; + } + + NS_RELEASE(event); + } + return result; +} + +bool WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue) { nsRefPtr<WorkerRunnable> event(aEvent); { MutexAutoLock lock(mMutex); if (mStatus == Dead) {
--- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -58,16 +58,17 @@ #include "EventTarget.h" #include "Queue.h" #include "WorkerFeature.h" class JSAutoStructuredCloneBuffer; class nsIDocument; class nsIPrincipal; +class nsIMemoryMultiReporter; class nsIScriptContext; class nsIURI; class nsPIDOMWindow; class nsITimer; namespace mozilla { namespace xpconnect { namespace memory { @@ -507,26 +508,29 @@ class WorkerPrivate : public WorkerPriva JSContext* mJSContext; // Things touched on worker thread only. nsTArray<ParentType*> mChildWorkers; nsTArray<WorkerFeature*> mFeatures; nsTArray<nsAutoPtr<TimeoutInfo> > mTimeouts; nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIMemoryMultiReporter> mMemoryReporter; mozilla::TimeStamp mKillTime; PRUint32 mErrorHandlerRecursionCount; PRUint32 mNextTimeoutId; Status mStatus; bool mSuspended; bool mTimerRunning; bool mRunningExpiredTimeouts; bool mCloseHandlerStarted; bool mCloseHandlerFinished; + bool mMemoryReporterRunning; + bool mMemoryReporterDisabled; #ifdef DEBUG nsCOMPtr<nsIThread> mThread; #endif public: ~WorkerPrivate(); @@ -649,17 +653,21 @@ public: void UpdateJSContextOptionsInternal(JSContext* aCx, PRUint32 aOptions); void ScheduleDeletion(bool aWasPending); bool - BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData); + BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData, + bool* aDisabled); + + bool + DisableMemoryReporter(); #ifdef JS_GC_ZEAL void UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal); #endif JSContext* GetJSContext() const @@ -738,16 +746,19 @@ private: mozilla::MutexAutoLock lock(mMutex); mStatus = Dead; mJSContext = nsnull; ClearQueue(&mQueue); ClearQueue(&mControlQueue); } + + bool + ProcessAllControlRunnables(); }; WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx); enum WorkerStructuredDataType { DOMWORKER_SCTAG_FILE = JS_SCTAG_USER_MIN + 0x1000,
--- a/dom/workers/test/chromeWorker_worker.js +++ b/dom/workers/test/chromeWorker_worker.js @@ -1,15 +1,20 @@ /** * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ if (!("ctypes" in self)) { throw "No ctypes!"; } +// Go ahead and verify that the ctypes lazy getter actually works. +if (ctypes.toString() != "[object ctypes]") { + throw "Bad ctypes object: " + ctypes.toString(); +} + onmessage = function(event) { let worker = new ChromeWorker("chromeWorker_subworker.js"); worker.onmessage = function(event) { postMessage(event.data); } worker.postMessage(event.data); }