Bug 267767 - Make XPCOM memory management functions frozen exports. r=darin sr=shaver

This commit is contained in:
bsmedberg@covad.net
2004-11-12 19:26:37 +00:00
parent b2cb136a41
commit f8e2073a8d
17 changed files with 480 additions and 504 deletions

View File

@@ -35,14 +35,22 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsXPCOM.h"
#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 "nsIObserverService.h"
#include "nsIRunnable.h"
#include "nsIServiceManager.h"
#include "nsISupportsArray.h"
#include "nsIThread.h"
#include "prmem.h"
#include "plevent.h"
#include "nsAlgorithm.h"
#include "nsAutoLock.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#if defined(XP_WIN)
@@ -56,216 +64,6 @@
#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
@@ -316,35 +114,66 @@ reallocator(void* ptr, PRSize size, PRUint32& counter, PRUint32 max)
#endif // NS_OUT_OF_MEMORY_TESTER
////////////////////////////////////////////////////////////////////////////////
#if defined(XDEBUG_waterson)
#define NS_TEST_MEMORY_FLUSHER
#endif
NS_IMETHODIMP_(void *)
#ifdef NS_MEMORY_FLUSHER_THREAD
/**
* 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
{
public:
// We don't use the generic macros because we are a special static object
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aResult);
NS_IMETHOD_(nsrefcnt) AddRef(void) { return 1; }
NS_IMETHOD_(nsrefcnt) Release(void) { return 1; }
NS_DECL_NSIRUNNABLE
nsresult Init();
void StopAndJoin();
private:
static PRBool sRunning;
static PRIntervalTime sTimeout;
static PRLock* sLock;
static PRCondVar* sCVar;
static nsIThread* sThread;
enum {
kInitialTimeout = 60 /*seconds*/
};
};
static MemoryFlusher sGlobalMemoryFlusher;
#endif // NS_MEMORY_FLUSHER_THREAD
static nsMemoryImpl sGlobalMemory;
NS_IMPL_QUERY_INTERFACE1(nsMemoryImpl, nsIMemory)
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;
return NS_Alloc(size);
}
NS_IMETHODIMP_(void *)
nsMemoryImpl::Realloc(void * ptr, PRSize size)
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;
return NS_Realloc(ptr, size);
}
NS_IMETHODIMP_(void)
nsMemoryImpl::Free(void * ptr)
nsMemoryImpl::Free(void* ptr)
{
PR_Free(ptr);
NS_Free(ptr);
}
NS_IMETHODIMP
@@ -397,6 +226,38 @@ nsMemoryImpl::IsLowMemory(PRBool *result)
return NS_OK;
}
nsresult
nsMemoryImpl::Startup()
{
#ifdef NS_MEMORY_FLUSHER_THREAD
sFlushLock = PR_NewLock();
if (!sFlushLock) return NS_ERROR_FAILURE;
return sGlobalMemoryFlusher.Init();
#else
return NS_OK;
#endif
}
void
nsMemoryImpl::Shutdown()
{
#ifdef NS_MEMORY_FLUSHER_THREAD
sGlobalMemoryFlusher.StopAndJoin();
#endif
if (sFlushLock) PR_DestroyLock(sFlushLock);
sFlushLock = nsnull;
}
nsresult
nsMemoryImpl::Create(nsISupports* outer, const nsIID& aIID, void **aResult)
{
NS_ENSURE_NO_AGGREGATION(outer);
return sGlobalMemory.QueryInterface(aIID, aResult);
}
nsresult
nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate)
{
@@ -427,18 +288,18 @@ nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate)
{
// Are we already flushing?
nsAutoLock l(mFlushLock);
if (mIsFlushing)
nsAutoLock l(sFlushLock);
if (sIsFlushing)
return NS_OK;
// Well, we are now!
mIsFlushing = PR_TRUE;
sIsFlushing = 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);
rv = RunFlushers(aReason);
}
else {
nsCOMPtr<nsIEventQueueService> eqs = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
@@ -446,10 +307,10 @@ nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate)
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;
PL_InitEvent(&sFlushEvent.mEvent, this, HandleFlushEvent, DestroyFlushEvent);
sFlushEvent.mReason = aReason;
rv = eq->PostEvent(NS_REINTERPRET_CAST(PLEvent*, &mFlushEvent));
rv = eq->PostEvent(NS_REINTERPRET_CAST(PLEvent*, &sFlushEvent));
}
}
}
@@ -458,17 +319,17 @@ nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate)
}
nsresult
nsMemoryImpl::RunFlushers(nsMemoryImpl* aSelf, const PRUnichar* aReason)
nsMemoryImpl::RunFlushers(const PRUnichar* aReason)
{
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os) {
os->NotifyObservers(aSelf, "memory-pressure", aReason);
os->NotifyObservers(this, "memory-pressure", aReason);
}
{
// Done flushing
nsAutoLock l(aSelf->mFlushLock);
aSelf->mIsFlushing = PR_FALSE;
nsAutoLock l(sFlushLock);
sIsFlushing = PR_FALSE;
}
return NS_OK;
@@ -477,10 +338,9 @@ nsMemoryImpl::RunFlushers(nsMemoryImpl* aSelf, const PRUnichar* aReason)
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);
sGlobalMemory.RunFlushers(event->mReason);
return 0;
}
@@ -490,59 +350,159 @@ nsMemoryImpl::DestroyFlushEvent(PLEvent* aEvent)
// no-op, since mEvent is a member of nsMemoryImpl
}
static void
EnsureGlobalMemoryService()
PRLock*
nsMemoryImpl::sFlushLock;
PRBool
nsMemoryImpl::sIsFlushing = PR_FALSE;
nsMemoryImpl::FlushEvent
nsMemoryImpl::sFlushEvent;
extern "C" NS_EXPORT void*
NS_Alloc(PRSize size)
{
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");
NS_ASSERTION(size, "NS_Alloc of size 0");
void* result = MALLOC1(size);
if (! result) {
// Request an asynchronous flush
sGlobalMemory.FlushMemory(NS_LITERAL_STRING("alloc-failure").get(), PR_FALSE);
}
return result;
}
nsresult
nsMemoryImpl::Startup()
extern "C" NS_EXPORT void*
NS_Realloc(void* ptr, PRSize size)
{
EnsureGlobalMemoryService();
if (! gMemory)
return NS_ERROR_FAILURE;
NS_ASSERTION(size, "NS_Realloc of size 0");
void* result = REALLOC1(ptr, size);
if (! result) {
// Request an asynchronous flush
sGlobalMemory.FlushMemory(NS_LITERAL_STRING("alloc-failure").get(), PR_FALSE);
}
return result;
}
extern "C" NS_EXPORT void
NS_Free(void* ptr)
{
PR_Free(ptr);
}
#ifdef NS_MEMORY_FLUSHER_THREAD
NS_IMPL_QUERY_INTERFACE1(MemoryFlusher, nsIRunnable);
NS_IMETHODIMP
MemoryFlusher::Run()
{
nsresult rv;
// Create and start a memory flusher thread
rv = MemoryFlusher::Create(&gMemory->mFlusher, gMemory);
if (NS_FAILED(rv)) return rv;
sRunning = PR_TRUE;
rv = NS_NewThread(getter_AddRefs(gMemory->mFlusherThread),
gMemory->mFlusher,
0, /* XXX use default stack size? */
PR_JOINABLE_THREAD);
while (1) {
PRStatus status;
if (NS_FAILED(rv)) return rv;
#endif
{
nsAutoLock l(sLock);
if (! sRunning) {
rv = NS_OK;
break;
}
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();
status = PR_WaitCondVar(sCVar, sTimeout);
}
if (status != PR_SUCCESS) {
rv = NS_ERROR_FAILURE;
break;
}
if (! sRunning) {
rv = NS_OK;
break;
}
PRBool isLowMemory;
rv = sGlobalMemory.IsLowMemory(&isLowMemory);
if (NS_FAILED(rv))
break;
#ifdef NS_TEST_MEMORY_FLUSHER
// Fire the flusher *every* time
isLowMemory = PR_TRUE;
#endif
NS_RELEASE(gMemory);
gMemory = nsnull;
if (isLowMemory) {
sGlobalMemory.FlushMemory(NS_LITERAL_STRING("low-memory").get(), PR_FALSE);
}
}
return NS_OK;
sRunning = PR_FALSE;
return rv;
}
nsresult
MemoryFlusher::Init()
{
sTimeout = PR_SecondsToInterval(kInitialTimeout);
sLock = PR_NewLock();
if (!sLock) return NS_ERROR_FAILURE;
sCVar = PR_NewCondVar(sLock);
if (!sCVar) return NS_ERROR_FAILURE;
return NS_NewThread(&sThread,
this,
0, /* XXX use default stack size? */
PR_JOINABLE_THREAD);
}
void
MemoryFlusher::StopAndJoin()
{
if (sRunning) {
{
nsAutoLock l(sLock);
sRunning = PR_FALSE;
PR_NotifyCondVar(sCVar);
}
if (sThread)
sThread->Join();
}
NS_IF_RELEASE(sThread);
if (sLock)
PR_DestroyLock(sLock);
if (sCVar)
PR_DestroyCondVar(sCVar);
}
PRBool
MemoryFlusher::sRunning;
PRIntervalTime
MemoryFlusher::sTimeout;
PRLock*
MemoryFlusher::sLock;
PRCondVar*
MemoryFlusher::sCVar;
nsIThread*
MemoryFlusher::sThread;
#endif // NS_MEMORY_FLUSHER_THREAD
nsresult
NS_GetMemoryManager(nsIMemory* *result)
{
return sGlobalMemory.QueryInterface(NS_GET_IID(nsIMemory), (void**) result);
}