diff --git a/mozilla/js/src/xpconnect/idl/nsIXPConnect.idl b/mozilla/js/src/xpconnect/idl/nsIXPConnect.idl index 26334ee4d4c..8fc00c4fb4d 100644 --- a/mozilla/js/src/xpconnect/idl/nsIXPConnect.idl +++ b/mozilla/js/src/xpconnect/idl/nsIXPConnect.idl @@ -564,6 +564,9 @@ interface nsIXPConnect : nsISupports getWrappedNativePrototype(in JSContextPtr aJSContext, in JSObjectPtr aScope, in nsIClassInfo aClassInfo); + + attribute PRBool collectGarbageOnMainThreadOnly; + attribute PRBool deferReleasesUntilAfterGarbageCollection; }; diff --git a/mozilla/js/src/xpconnect/shell/xpcshell.cpp b/mozilla/js/src/xpconnect/shell/xpcshell.cpp index 1d047349af1..48092062605 100644 --- a/mozilla/js/src/xpconnect/shell/xpcshell.cpp +++ b/mozilla/js/src/xpconnect/shell/xpcshell.cpp @@ -877,6 +877,8 @@ main(int argc, char **argv) NS_STATIC_CAST(nsIXPCSecurityManager*, new FullTrustSecMan()); xpc->SetSecurityManagerForJSContext(jscontext, secman, 0); +// xpc->SetCollectGarbageOnMainThreadOnly(PR_TRUE); +// xpc->SetDeferReleasesUntilAfterGarbageCollection(PR_TRUE); #ifdef TEST_TranslateThis nsCOMPtr diff --git a/mozilla/js/src/xpconnect/src/nsXPConnect.cpp b/mozilla/js/src/xpconnect/src/nsXPConnect.cpp index b9852dcebcc..28a678d3cf4 100644 --- a/mozilla/js/src/xpconnect/src/nsXPConnect.cpp +++ b/mozilla/js/src/xpconnect/src/nsXPConnect.cpp @@ -41,7 +41,8 @@ NS_IMPL_THREADSAFE_ISUPPORTS2(nsXPConnect,nsIXPConnect,nsISupportsWeakReference) nsXPConnect* nsXPConnect::gSelf = nsnull; -JSBool nsXPConnect::gOnceAliveNowDead = JS_FALSE; +JSBool nsXPConnect::gOnceAliveNowDead = JS_FALSE; +PRThread* nsXPConnect::gMainThread = nsnull; const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1"; const char XPC_RUNTIME_CONTRACTID[] = "@mozilla.org/js/xpc/RuntimeService;1"; @@ -314,6 +315,19 @@ nsXPConnect::CreateRuntime() return nsnull != mRuntime; } +// static +PRThread* +nsXPConnect::FindMainThread() +{ + nsCOMPtr t; + nsresult rv; + rv = nsIThread::GetMainThread(getter_AddRefs(t)); + NS_ASSERTION(NS_SUCCEEDED(rv) && t, "bad"); + rv = t->GetPRThread(&gMainThread); + NS_ASSERTION(NS_SUCCEEDED(rv) && gMainThread, "bad"); + return gMainThread; +} + /***************************************************************************/ /***************************************************************************/ // nsIXPConnect interface methods... @@ -412,7 +426,10 @@ nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext, // voodoo to fixup scoping and parenting... JS_SetParent(aJSContext, globalJSObj, nsnull); - JS_SetGlobalObject(aJSContext, globalJSObj); + + JSObject* oldGlobal = JS_GetGlobalObject(aJSContext); + if(!oldGlobal || oldGlobal == tempGlobal) + JS_SetGlobalObject(aJSContext, globalJSObj); if(aCallJS_InitStandardClasses && !JS_InitStandardClasses(aJSContext, globalJSObj)) @@ -667,12 +684,10 @@ NS_IMETHODIMP nsXPConnect::SetDefaultSecurityManager(nsIXPCSecurityManager *aManager, PRUint16 flags) { -#if 1 NS_IF_ADDREF(aManager); NS_IF_RELEASE(mDefaultSecurityManager); mDefaultSecurityManager = aManager; mDefaultSecurityManagerFlags = flags; -#endif return NS_OK; } @@ -900,6 +915,52 @@ nsXPConnect::GetWrappedNativePrototype(JSContext * aJSContext, return NS_OK; } +/* attribute PRBool collectGarbageOnMainThreadOnly; */ +NS_IMETHODIMP +nsXPConnect::GetCollectGarbageOnMainThreadOnly(PRBool *aCollectGarbageOnMainThreadOnly) +{ + XPCJSRuntime* rt = GetRuntime(); + if(!rt) + return UnexpectedFailure(NS_ERROR_FAILURE); + + *aCollectGarbageOnMainThreadOnly = rt->GetMainThreadOnlyGC(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::SetCollectGarbageOnMainThreadOnly(PRBool aCollectGarbageOnMainThreadOnly) +{ + XPCJSRuntime* rt = GetRuntime(); + if(!rt) + return UnexpectedFailure(NS_ERROR_FAILURE); + + rt->SetMainThreadOnlyGC(aCollectGarbageOnMainThreadOnly); + return NS_OK; +} + +/* attribute PRBool deferReleasesUntilAfterGarbageCollection; */ +NS_IMETHODIMP +nsXPConnect::GetDeferReleasesUntilAfterGarbageCollection(PRBool *aDeferReleasesUntilAfterGarbageCollection) +{ + XPCJSRuntime* rt = GetRuntime(); + if(!rt) + return UnexpectedFailure(NS_ERROR_FAILURE); + + *aDeferReleasesUntilAfterGarbageCollection = rt->GetDeferReleases(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::SetDeferReleasesUntilAfterGarbageCollection(PRBool aDeferReleasesUntilAfterGarbageCollection) +{ + XPCJSRuntime* rt = GetRuntime(); + if(!rt) + return UnexpectedFailure(NS_ERROR_FAILURE); + + rt->SetDeferReleases(aDeferReleasesUntilAfterGarbageCollection); + return NS_OK; +} + /* void debugDump (in short depth); */ NS_IMETHODIMP nsXPConnect::DebugDump(PRInt16 depth) diff --git a/mozilla/js/src/xpconnect/src/xpcjsruntime.cpp b/mozilla/js/src/xpconnect/src/xpcjsruntime.cpp index 3b98beb513e..4ded32dde3f 100644 --- a/mozilla/js/src/xpconnect/src/xpcjsruntime.cpp +++ b/mozilla/js/src/xpconnect/src/xpcjsruntime.cpp @@ -204,22 +204,32 @@ DyingProtoKiller(JSDHashTable *table, JSDHashEntryHdr *hdr, // static JSBool XPCJSRuntime::GCCallback(JSContext *cx, JSGCStatus status) { + nsVoidArray* dyingWrappedJSArray; + XPCJSRuntime* self = nsXPConnect::GetRuntime(); if(self) { - nsVoidArray* dyingWrappedJSArray = &self->mWrappedJSToReleaseArray; - switch(status) { case JSGC_BEGIN: { - // do nothing (yet)... + if(self->GetMainThreadOnlyGC() && + PR_GetCurrentThread() != nsXPConnect::GetMainThread()) + { + return JS_FALSE; + } break; } case JSGC_MARK_END: { + NS_ASSERTION(!self->mDoingFinalization, "bad state"); + + dyingWrappedJSArray = &self->mWrappedJSToReleaseArray; { - XPCAutoLock lock(self->GetMapLock()); // lock the wrapper map + XPCLock* lock = self->GetMainThreadOnlyGC() ? + nsnull : self->GetMapLock(); + + XPCAutoLock al(lock); // lock the wrapper map if necessary JSDyingJSObjectData data = {cx, dyingWrappedJSArray}; // Add any wrappers whose JSObjects are to be finalized to @@ -241,10 +251,13 @@ JSBool XPCJSRuntime::GCCallback(JSContext *cx, JSGCStatus status) // Find dying scopes... XPCWrappedNativeScope::FinishedMarkPhaseOfGC(cx, self); + self->mDoingFinalization = JS_TRUE; break; } case JSGC_FINALIZE_END: { + NS_ASSERTION(self->mDoingFinalization, "bad state"); + self->mDoingFinalization = JS_FALSE; #ifdef XPC_REPORT_NATIVE_INTERFACE_AND_SET_FLUSHING printf("--------------------------------------------------------------\n"); @@ -422,19 +435,56 @@ JSBool XPCJSRuntime::GCCallback(JSContext *cx, JSGCStatus status) // Release all the members whose JSObjects are now known // to be dead. - // XXX We ought to enter and exit a lock and pick these - // elements off one at a time! - - for(PRInt32 i = dyingWrappedJSArray->Count() - 1; i >= 0; i--) + dyingWrappedJSArray = &self->mWrappedJSToReleaseArray; + XPCLock* lock = self->GetMainThreadOnlyGC() ? + nsnull : self->GetMapLock(); + while(1) { - nsXPCWrappedJS* wrapper = - NS_REINTERPRET_CAST(nsXPCWrappedJS*, - dyingWrappedJSArray->ElementAt(i)); - + nsXPCWrappedJS* wrapper; + { + XPCAutoLock al(lock); // lock if necessary + PRInt32 count = dyingWrappedJSArray->Count(); + if(!count) + { + dyingWrappedJSArray->Compact(); + break; + } + wrapper = NS_REINTERPRET_CAST(nsXPCWrappedJS*, + dyingWrappedJSArray->ElementAt(count-1)); + dyingWrappedJSArray->RemoveElementAt(count-1); + } NS_RELEASE(wrapper); } - dyingWrappedJSArray->Clear(); + // Do any deferred released of native objects. + if(self->GetDeferReleases()) + { + nsVoidArray* array = &self->mNativesToReleaseArray; +#ifdef XPC_TRACK_DEFERRED_RELEASES + printf("XPC - Begin deferred Release of %d nsISupports pointers\n", + array->Count()); +#endif + while(1) + { + nsISupports* obj; + { + XPCAutoLock al(lock); // lock if necessary + PRInt32 count = array->Count(); + if(!count) + { + array->Compact(); + break; + } + obj = NS_REINTERPRET_CAST(nsISupports*, + array->ElementAt(count-1)); + array->RemoveElementAt(count-1); + } + NS_RELEASE(obj); + } +#ifdef XPC_TRACK_DEFERRED_RELEASES + printf("XPC - End deferred Releases\n"); +#endif + } break; } default: @@ -572,6 +622,16 @@ XPCJSRuntime::~XPCJSRuntime() delete mNativeScriptableSharedMap; } + if(mDyingWrappedNativeProtoMap) + { +#ifdef XPC_DUMP_AT_SHUTDOWN + uint32 count = mDyingWrappedNativeProtoMap->Count(); + if(count) + printf("deleting XPCJSRuntime with %d live but dying XPCWrappedNativeProto\n", (int)count); +#endif + delete mDyingWrappedNativeProtoMap; + } + // unwire the readable/JSString sharing magic XPCStringConvert::ShutdownDOMStringFinalizer(); } @@ -591,9 +651,12 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect, mNativeScriptableSharedMap(XPCNativeScriptableSharedMap::newMap(XPC_NATIVE_JSCLASS_MAP_SIZE)), mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_SIZE)), mMapLock(XPCAutoLock::NewLock("XPCJSRuntime::mMapLock")), - mWrappedJSToReleaseArray() + mWrappedJSToReleaseArray(), + mNativesToReleaseArray(), + mMainThreadOnlyGC(JS_FALSE), + mDeferReleases(JS_FALSE), + mDoingFinalization(JS_FALSE) { - #ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN DEBUG_WrappedNativeHashtable = JS_NewDHashTable(JS_DHashGetStubOps(), nsnull, @@ -773,6 +836,19 @@ XPCJSRuntime::GenerateStringIDs(JSContext* cx) return JS_TRUE; } +JSBool +XPCJSRuntime::DeferredRelease(nsISupports* obj) +{ + NS_ASSERTION(obj, "bad param"); + NS_ASSERTION(GetDeferReleases(), "bad call"); + + XPCLock* lock = GetMainThreadOnlyGC() ? nsnull : GetMapLock(); + { + XPCAutoLock al(lock); // lock if necessary + return mNativesToReleaseArray.AppendElement(obj); + } +} + /***************************************************************************/ #ifdef DEBUG diff --git a/mozilla/js/src/xpconnect/src/xpcprivate.h b/mozilla/js/src/xpconnect/src/xpcprivate.h index 596215727a9..5daf2e5c554 100644 --- a/mozilla/js/src/xpconnect/src/xpcprivate.h +++ b/mozilla/js/src/xpconnect/src/xpcprivate.h @@ -125,6 +125,7 @@ #define XPC_TRACK_WRAPPER_STATS #define XPC_TRACK_SCOPE_STATS #define XPC_TRACK_PROTO_STATS +#define XPC_TRACK_DEFERRED_RELEASES #define XPC_CHECK_WRAPPERS_AT_SHUTDOWN #define XPC_REPORT_SHADOWED_WRAPPED_NATIVE_MEMBERS #define XPC_CHECK_CLASSINFO_CLAIMS @@ -380,6 +381,9 @@ public: static JSBool IsISupportsDescendant(nsIInterfaceInfo* info); + static PRThread* GetMainThread() + {return gMainThread ? gMainThread : FindMainThread();} + nsIXPCSecurityManager* GetDefaultSecurityManager() const {return mDefaultSecurityManager;} @@ -405,10 +409,13 @@ private: JSBool EnsureRuntime() {return mRuntime ? JS_TRUE : CreateRuntime();} JSBool CreateRuntime(); + static PRThread* FindMainThread(); + private: // Singleton instance static nsXPConnect* gSelf; static JSBool gOnceAliveNowDead; + static PRThread* gMainThread; XPCJSRuntime* mRuntime; nsIInterfaceInfoManager* mInterfaceInfoManager; @@ -467,6 +474,20 @@ public: XPCContext* GetXPCContext(JSContext* cx); XPCContext* SyncXPCContextList(JSContext* cx = nsnull); + JSBool GetMainThreadOnlyGC() const {return mMainThreadOnlyGC;} + void SetMainThreadOnlyGC(JSBool b) {mMainThreadOnlyGC = b;} + + JSBool GetDeferReleases() const {return mDeferReleases;} + void SetDeferReleases(JSBool b) + {/* If deferring is turned off while any are pending they'll leak! */ + NS_ASSERTION((mDeferReleases && b) || + !mNativesToReleaseArray.Count(), "bad"); + mDeferReleases = b;} + + JSBool DeferredRelease(nsISupports* obj); + + JSBool GetDoingFinalization() const {return mDoingFinalization;} + // Mapping of often used strings to jsid atoms that live 'forever'. // // To add a new string: add to this list and to XPCJSRuntime::mStrings @@ -554,6 +575,10 @@ private: XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap; XPCLock* mMapLock; nsVoidArray mWrappedJSToReleaseArray; + nsVoidArray mNativesToReleaseArray; + JSBool mMainThreadOnlyGC; + JSBool mDeferReleases; + JSBool mDoingFinalization; }; /***************************************************************************/ diff --git a/mozilla/js/src/xpconnect/src/xpcwrappednative.cpp b/mozilla/js/src/xpconnect/src/xpcwrappednative.cpp index 5bdc5e85042..e98b992b0b8 100644 --- a/mozilla/js/src/xpconnect/src/xpcwrappednative.cpp +++ b/mozilla/js/src/xpconnect/src/xpcwrappednative.cpp @@ -492,10 +492,25 @@ XPCWrappedNative::~XPCWrappedNative() map->Remove(this); } - NS_IF_RELEASE(mIdentity); + if(mIdentity) + { + XPCJSRuntime* rt = GetRuntime(); + if(rt && rt->GetDeferReleases() && rt->GetDoingFinalization()) + { + if(!rt->DeferredRelease(mIdentity)) + { + NS_WARNING("Failed to append object for deferred release."); + // XXX do we really want to do this??? + NS_RELEASE(mIdentity); + } + } + else + { + NS_RELEASE(mIdentity); + } + } } - // This is factored out so that it can be called publicly // static nsresult @@ -695,14 +710,8 @@ XPCWrappedNative::Init(XPCCallContext& ccx, JSObject* parent, #ifdef XPC_CHECK_WRAPPER_THREADSAFETY if(!gMainThread) - { - nsCOMPtr t; - nsresult rv; - rv = nsIThread::GetMainThread(getter_AddRefs(t)); - NS_ASSERTION(NS_SUCCEEDED(rv) && t, "bad"); - rv = t->GetPRThread(&gMainThread); - NS_ASSERTION(NS_SUCCEEDED(rv) && gMainThread, "bad"); - } + gMainThread = nsXPConnect::GetMainThread(); + mThread = PR_GetCurrentThread(); if(HasProto() && GetProto()->ClassIsMainThreadOnly() && gMainThread != mThread) @@ -839,7 +848,25 @@ XPCWrappedNative::FlatJSObjectFinalized(JSContext *cx, JSObject *obj) nsISupports* obj = to->GetNative(); if(obj) { - obj->Release(); +#ifdef XP_WIN + // Try to detect free'd pointer + NS_ASSERTION(*(int*)obj != 0xdddddddd, "bad pointer!"); + NS_ASSERTION(*(int*)obj != 0, "bad pointer!"); +#endif + XPCJSRuntime* rt = GetRuntime(); + if(rt && rt->GetDeferReleases()) + { + if(!rt->DeferredRelease(obj)) + { + NS_WARNING("Failed to append object for deferred release."); + // XXX do we really want to do this??? + obj->Release(); + } + } + else + { + obj->Release(); + } to->SetNative(nsnull); } @@ -849,6 +876,14 @@ XPCWrappedNative::FlatJSObjectFinalized(JSContext *cx, JSObject *obj) //This makes IsValid return false from now on... mFlatJSObject = nsnull; + + NS_ASSERTION(mIdentity, "bad pointer!"); +#ifdef XP_WIN + // Try to detect free'd pointer + NS_ASSERTION(*(int*)mIdentity != 0xdddddddd, "bad pointer!"); + NS_ASSERTION(*(int*)mIdentity != 0, "bad pointer!"); +#endif + Release(); }