diff options
Diffstat (limited to 'src/libs/xpcom18a4/xpcom/base/nsMemoryImpl.cpp')
-rw-r--r-- | src/libs/xpcom18a4/xpcom/base/nsMemoryImpl.cpp | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/src/libs/xpcom18a4/xpcom/base/nsMemoryImpl.cpp b/src/libs/xpcom18a4/xpcom/base/nsMemoryImpl.cpp new file mode 100644 index 00000000..8aa16e58 --- /dev/null +++ b/src/libs/xpcom18a4/xpcom/base/nsMemoryImpl.cpp @@ -0,0 +1,548 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla 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/MPL/ + * + * 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 the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#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_WIN) +#include <windows.h> +#define NS_MEMORY_FLUSHER_THREAD +#elif defined(XP_MAC) +#include <MacMemory.h> +#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); + + enum { + kInitialTimeout = 60 /*seconds*/ + }; + +private: + ~MemoryFlusher(); + +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) +{ +} + +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_LITERAL_STRING("low-memory").get(), PR_FALSE); + } + } + + mRunning = PR_FALSE; + + return rv; +} + + +nsresult +MemoryFlusher::Stop() +{ + if (mRunning) { + nsAutoLock l(mLock); + mRunning = PR_FALSE; + PR_NotifyCondVar(mCVar); + } + + return NS_OK; +} + +//---------------------------------------------------------------------- + +nsMemoryImpl* gMemory = nsnull; + +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); + if (gMemory && NS_SUCCEEDED(gMemory->QueryInterface(aIID, aInstancePtr))) + return NS_OK; + + 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) +{ +} + +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) +{ + NS_ASSERTION(size, "nsMemoryImpl::Alloc of 0"); + void* result = MALLOC1(size); + if (! result) { + // Request an asynchronous flush + FlushMemory(NS_LITERAL_STRING("alloc-failure").get(), 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_LITERAL_STRING("alloc-failure").get(), PR_FALSE); + } + return result; +} + +NS_IMETHODIMP_(void) +nsMemoryImpl::Free(void * ptr) +{ + PR_Free(ptr); +} + +NS_IMETHODIMP +nsMemoryImpl::HeapMinimize(PRBool aImmediate) +{ + return FlushMemory(NS_LITERAL_STRING("heap-minimize").get(), aImmediate); +} + +NS_IMETHODIMP +nsMemoryImpl::IsLowMemory(PRBool *result) +{ +#if defined(XP_WIN) + 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<nsIThread> main; + rv = nsIThread::GetMainThread(getter_AddRefs(main)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIThread> 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<nsIEventQueueService> eqs = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv); + if (eqs) { + nsCOMPtr<nsIEventQueue> 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<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1"); + if (os) { + os->NotifyObservers(aSelf, "memory-pressure", 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 +} + +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; +} |