/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsMemoryImpl.h" #include "prmem.h" #include "nsAlgorithm.h" #include "nsIServiceManager.h" #include "nsIObserverService.h" #include "nsAutoLock.h" #include "nsIThread.h" #include "nsIEventQueueService.h" #include "nsString.h" #if defined(XP_PC) && !defined(XP_OS2) #include #define NS_MEMORY_FLUSHER_THREAD #elif defined(XP_MAC) #include #define NS_MEMORY_FLUSHER_THREAD #else // Need to implement the nsIMemory::IsLowMemory() predicate #undef NS_MEMORY_FLUSHER_THREAD #endif //---------------------------------------------------------------------- #if defined(XDEBUG_waterson) #define NS_TEST_MEMORY_FLUSHER #endif /** * A runnable that is used to periodically check the status * of the system, determine if too much memory is in use, * and if so, trigger a "memory flush". */ class MemoryFlusher : public nsIRunnable { protected: nsMemoryImpl* mMemoryImpl; // WEAK, it owns us. PRBool mRunning; PRIntervalTime mTimeout; PRLock* mLock; PRCondVar* mCVar; MemoryFlusher(nsMemoryImpl* aMemoryImpl); virtual ~MemoryFlusher(); enum { kInitialTimeout = 60 /*seconds*/ }; public: /** * Create a memory flusher. * @param aResult the memory flusher * @param aMemoryImpl the owning nsMemoryImpl object * @return NS_OK if the memory flusher was created successfully */ static nsresult Create(MemoryFlusher** aResult, nsMemoryImpl* aMemoryImpl); NS_DECL_ISUPPORTS NS_DECL_NSIRUNNABLE /** * Stop the memory flusher. */ nsresult Stop(); }; MemoryFlusher::MemoryFlusher(nsMemoryImpl* aMemoryImpl) : mMemoryImpl(aMemoryImpl), mRunning(PR_FALSE), mTimeout(PR_SecondsToInterval(kInitialTimeout)), mLock(nsnull), mCVar(nsnull) { NS_INIT_REFCNT(); } MemoryFlusher::~MemoryFlusher() { if (mLock) PR_DestroyLock(mLock); if (mCVar) PR_DestroyCondVar(mCVar); } nsresult MemoryFlusher::Create(MemoryFlusher** aResult, nsMemoryImpl* aMemoryImpl) { MemoryFlusher* result = new MemoryFlusher(aMemoryImpl); if (! result) return NS_ERROR_OUT_OF_MEMORY; do { if ((result->mLock = PR_NewLock()) == nsnull) break; if ((result->mCVar = PR_NewCondVar(result->mLock)) == nsnull) break; NS_ADDREF(*aResult = result); return NS_OK; } while (0); // Something bad happened if we get here... delete result; return NS_ERROR_OUT_OF_MEMORY; } NS_IMPL_THREADSAFE_ISUPPORTS1(MemoryFlusher, nsIRunnable) NS_IMETHODIMP MemoryFlusher::Run() { nsresult rv; mRunning = PR_TRUE; while (1) { PRStatus status; { nsAutoLock l(mLock); if (! mRunning) { rv = NS_OK; break; } status = PR_WaitCondVar(mCVar, mTimeout); } if (status != PR_SUCCESS) { rv = NS_ERROR_FAILURE; break; } if (! mRunning) { rv = NS_OK; break; } PRBool isLowMemory; rv = mMemoryImpl->IsLowMemory(&isLowMemory); if (NS_FAILED(rv)) break; #ifdef NS_TEST_MEMORY_FLUSHER // Fire the flusher *every* time isLowMemory = PR_TRUE; #endif if (isLowMemory) { mMemoryImpl->FlushMemory(NS_MEMORY_PRESSURE_LOW_MEMORY, PR_FALSE); } } mRunning = PR_FALSE; return rv; } nsresult MemoryFlusher::Stop() { if (mRunning) { nsAutoLock l(mLock); mRunning = PR_FALSE; PR_NotifyCondVar(mCVar); } return NS_OK; } //---------------------------------------------------------------------- NS_IMPL_THREADSAFE_ISUPPORTS1(nsMemoryImpl, nsIMemory) NS_METHOD nsMemoryImpl::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) { NS_ENSURE_ARG_POINTER(aInstancePtr); NS_ENSURE_PROPER_AGGREGATION(outer, aIID); nsMemoryImpl* mm = new nsMemoryImpl(); if (mm == NULL) return NS_ERROR_OUT_OF_MEMORY; nsresult rv; do { rv = mm->QueryInterface(aIID, aInstancePtr); if (NS_FAILED(rv)) break; rv = NS_ERROR_OUT_OF_MEMORY; mm->mFlushLock = PR_NewLock(); if (! mm->mFlushLock) break; rv = NS_OK; } while (0); if (NS_FAILED(rv)) delete mm; return rv; } nsMemoryImpl::nsMemoryImpl() : mFlusher(nsnull), mFlushLock(nsnull), mIsFlushing(PR_FALSE) { NS_INIT_REFCNT(); } nsMemoryImpl::~nsMemoryImpl() { if (mFlushLock) PR_DestroyLock(mFlushLock); } //////////////////////////////////////////////////////////////////////////////// // Define NS_OUT_OF_MEMORY_TESTER if you want to force memory failures #ifdef DEBUG_xwarren #define NS_OUT_OF_MEMORY_TESTER #endif #ifdef NS_OUT_OF_MEMORY_TESTER // flush memory one in this number of times: #define NS_FLUSH_FREQUENCY 100000 // fail allocation one in this number of flushes: #define NS_FAIL_FREQUENCY 10 PRUint32 gFlushFreq = 0; PRUint32 gFailFreq = 0; static void* mallocator(PRSize size, PRUint32& counter, PRUint32 max) { if (counter++ >= max) { counter = 0; NS_ASSERTION(0, "about to fail allocation... watch out"); return nsnull; } return PR_Malloc(size); } static void* reallocator(void* ptr, PRSize size, PRUint32& counter, PRUint32 max) { if (counter++ >= max) { counter = 0; NS_ASSERTION(0, "about to fail reallocation... watch out"); return nsnull; } return PR_Realloc(ptr, size); } #define MALLOC1(s) mallocator(s, gFlushFreq, NS_FLUSH_FREQUENCY) #define REALLOC1(p, s) reallocator(p, s, gFlushFreq, NS_FLUSH_FREQUENCY) #else #define MALLOC1(s) PR_Malloc(s) #define REALLOC1(p, s) PR_Realloc(p, s) #endif // NS_OUT_OF_MEMORY_TESTER //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP_(void *) nsMemoryImpl::Alloc(PRSize size) { void* result = MALLOC1(size); if (! result) { // Request an asynchronous flush FlushMemory(NS_MEMORY_PRESSURE_ALLOC_FAILURE, PR_FALSE); } return result; } NS_IMETHODIMP_(void *) nsMemoryImpl::Realloc(void * ptr, PRSize size) { void* result = REALLOC1(ptr, size); if (! result) { // Request an asynchronous flush FlushMemory(NS_MEMORY_PRESSURE_ALLOC_FAILURE, PR_FALSE); } return result; } NS_IMETHODIMP_(void) nsMemoryImpl::Free(void * ptr) { PR_Free(ptr); } NS_IMETHODIMP nsMemoryImpl::HeapMinimize(PRBool aImmediate) { return FlushMemory(NS_MEMORY_PRESSURE_HEAP_MINIMIZE, aImmediate); } NS_IMETHODIMP nsMemoryImpl::IsLowMemory(PRBool *result) { #if defined(XP_PC) && !defined(XP_OS2) MEMORYSTATUS stat; GlobalMemoryStatus(&stat); *result = ((float)stat.dwAvailPageFile / stat.dwTotalPageFile) < 0.1; #elif defined(XP_MAC) const long kReserveHeapFreeSpace = (256 * 1024); const long kReserveHeapContigSpace = (128 * 1024); long totalSpace, contiguousSpace; // this call measures how much memory would be available if the OS // purged. Despite the name, it does not purge (that happens // automatically when heap space is low). ::PurgeSpace(&totalSpace, &contiguousSpace); if (totalSpace < kReserveHeapFreeSpace || contiguousSpace < kReserveHeapContigSpace) { NS_WARNING("Found that heap mem is low"); *result = PR_TRUE; return NS_OK; } // see how much temp mem is available (since our allocators allocate 1Mb chunks // in temp mem. We don't use TempMaxMem() (to get contig space) here, because it // compacts the application heap, so can be slow. const long kReserveTempFreeSpace = (2 * 1024 * 1024); // 2Mb long totalTempSpace = ::TempFreeMem(); if (totalTempSpace < kReserveTempFreeSpace) { NS_WARNING("Found that temp mem is low"); *result = PR_TRUE; return NS_OK; } *result = PR_FALSE; #else *result = PR_FALSE; #endif return NS_OK; } nsresult nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate) { nsresult rv; if (aImmediate) { // They've asked us to run the flusher *immediately*. We've // got to be on the UI main thread for us to be able to do // that...are we? PRBool isOnUIThread = PR_FALSE; nsCOMPtr main; rv = nsIThread::GetMainThread(getter_AddRefs(main)); if (NS_SUCCEEDED(rv)) { nsCOMPtr current; rv = nsIThread::GetCurrent(getter_AddRefs(current)); if (NS_SUCCEEDED(rv)) { if (current == main) isOnUIThread = PR_TRUE; } } if (! isOnUIThread) { NS_ERROR("can't synchronously flush memory: not on UI thread"); return NS_ERROR_FAILURE; } } { // Are we already flushing? nsAutoLock l(mFlushLock); if (mIsFlushing) return NS_OK; // Well, we are now! mIsFlushing = PR_TRUE; } // Run the flushers immediately if we can; otherwise, proxy to the // UI thread an run 'em asynchronously. if (aImmediate) { rv = RunFlushers(this, aReason); } else { nsCOMPtr eqs = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv); if (eqs) { nsCOMPtr eq; rv = eqs->GetThreadEventQueue(NS_UI_THREAD, getter_AddRefs(eq)); if (NS_SUCCEEDED(rv)) { PL_InitEvent(&mFlushEvent.mEvent, this, HandleFlushEvent, DestroyFlushEvent); mFlushEvent.mReason = aReason; rv = eq->PostEvent(NS_REINTERPRET_CAST(PLEvent*, &mFlushEvent)); } } } return rv; } nsresult nsMemoryImpl::RunFlushers(nsMemoryImpl* aSelf, const PRUnichar* aReason) { nsCOMPtr os = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); if (os) { os->Notify(aSelf, NS_MEMORY_PRESSURE_TOPIC, aReason); } { // Done flushing nsAutoLock l(aSelf->mFlushLock); aSelf->mIsFlushing = PR_FALSE; } return NS_OK; } void* nsMemoryImpl::HandleFlushEvent(PLEvent* aEvent) { nsMemoryImpl* self = NS_STATIC_CAST(nsMemoryImpl*, PL_GetEventOwner(aEvent)); FlushEvent* event = NS_REINTERPRET_CAST(FlushEvent*, aEvent); RunFlushers(self, event->mReason); return 0; } void nsMemoryImpl::DestroyFlushEvent(PLEvent* aEvent) { // no-op, since mEvent is a member of nsMemoryImpl } nsMemoryImpl* gMemory = nsnull; static void EnsureGlobalMemoryService() { if (gMemory) return; nsresult rv = nsMemoryImpl::Create(nsnull, NS_GET_IID(nsIMemory), (void**)&gMemory); NS_ASSERTION(NS_SUCCEEDED(rv), "nsMemoryImpl::Create failed"); NS_ASSERTION(gMemory, "improper xpcom initialization"); } nsresult nsMemoryImpl::Startup() { EnsureGlobalMemoryService(); if (! gMemory) return NS_ERROR_FAILURE; #ifdef NS_MEMORY_FLUSHER_THREAD nsresult rv; // Create and start a memory flusher thread rv = MemoryFlusher::Create(&gMemory->mFlusher, gMemory); if (NS_FAILED(rv)) return rv; rv = NS_NewThread(getter_AddRefs(gMemory->mFlusherThread), gMemory->mFlusher, 0, /* XXX use default stack size? */ PR_JOINABLE_THREAD); if (NS_FAILED(rv)) return rv; #endif return NS_OK; } nsresult nsMemoryImpl::Shutdown() { if (gMemory) { #ifdef NS_MEMORY_FLUSHER_THREAD if (gMemory->mFlusher) { // Stop the runnable... gMemory->mFlusher->Stop(); NS_RELEASE(gMemory->mFlusher); // ...and wait for the thread to exit if (gMemory->mFlusherThread) gMemory->mFlusherThread->Join(); } #endif NS_RELEASE(gMemory); gMemory = nsnull; } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsMemory static helper routines NS_EXPORT void* nsMemory::Alloc(PRSize size) { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } return gMemory->Alloc(size); } NS_EXPORT void* nsMemory::Realloc(void* ptr, PRSize size) { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } return gMemory->Realloc(ptr, size); } NS_EXPORT void nsMemory::Free(void* ptr) { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } gMemory->Free(ptr); } NS_EXPORT nsresult nsMemory::HeapMinimize(PRBool aImmediate) { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } return gMemory->HeapMinimize(aImmediate); } NS_EXPORT void* nsMemory::Clone(const void* ptr, PRSize size) { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } void* newPtr = gMemory->Alloc(size); if (newPtr) memcpy(newPtr, ptr, size); return newPtr; } NS_EXPORT nsIMemory* nsMemory::GetGlobalMemoryService() { if (gMemory == nsnull) { EnsureGlobalMemoryService(); } NS_ADDREF(gMemory); return gMemory; } //----------------------------------------------------------------------