source: trunk/third/firefox/xpcom/base/nsMemoryImpl.cpp @ 21695

Revision 21695, 13.6 KB checked in by rbasch, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21694, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/* ***** BEGIN LICENSE BLOCK *****
3 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Netscape Public License
6 * Version 1.1 (the "License"); you may not use this file except in
7 * compliance with the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/NPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is mozilla.org code.
16 *
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the NPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the NPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38#include "nsMemoryImpl.h"
39#include "prmem.h"
40#include "nsAlgorithm.h"
41#include "nsIServiceManager.h"
42#include "nsIObserverService.h"
43#include "nsAutoLock.h"
44#include "nsIThread.h"
45#include "nsIEventQueueService.h"
46#include "nsString.h"
47
48#if defined(XP_WIN)
49#include <windows.h>
50#define NS_MEMORY_FLUSHER_THREAD
51#elif defined(XP_MAC)
52#include <MacMemory.h>
53#define NS_MEMORY_FLUSHER_THREAD
54#else
55// Need to implement the nsIMemory::IsLowMemory() predicate
56#undef NS_MEMORY_FLUSHER_THREAD
57#endif
58
59//----------------------------------------------------------------------
60
61#if defined(XDEBUG_waterson)
62#define NS_TEST_MEMORY_FLUSHER
63#endif
64
65/**
66 * A runnable that is used to periodically check the status
67 * of the system, determine if too much memory is in use,
68 * and if so, trigger a "memory flush".
69 */
70class MemoryFlusher : public nsIRunnable
71{
72protected:
73    nsMemoryImpl*  mMemoryImpl; // WEAK, it owns us.
74    PRBool         mRunning;
75    PRIntervalTime mTimeout;
76    PRLock*        mLock;
77    PRCondVar*     mCVar;
78   
79    MemoryFlusher(nsMemoryImpl* aMemoryImpl);
80
81    enum {
82        kInitialTimeout = 60 /*seconds*/
83    };
84
85private:
86    ~MemoryFlusher();
87
88public:
89    /**
90     * Create a memory flusher.
91     * @param aResult the memory flusher
92     * @param aMemoryImpl the owning nsMemoryImpl object
93     * @return NS_OK if the memory flusher was created successfully
94     */
95    static nsresult
96    Create(MemoryFlusher** aResult, nsMemoryImpl* aMemoryImpl);
97
98    NS_DECL_ISUPPORTS
99    NS_DECL_NSIRUNNABLE
100
101    /**
102     * Stop the memory flusher.
103     */
104    nsresult Stop();
105};
106
107
108MemoryFlusher::MemoryFlusher(nsMemoryImpl* aMemoryImpl)
109    : mMemoryImpl(aMemoryImpl),
110      mRunning(PR_FALSE),
111      mTimeout(PR_SecondsToInterval(kInitialTimeout)),
112      mLock(nsnull),
113      mCVar(nsnull)
114{
115}
116
117MemoryFlusher::~MemoryFlusher()
118{
119    if (mLock)
120        PR_DestroyLock(mLock);
121
122    if (mCVar)
123        PR_DestroyCondVar(mCVar);
124}
125
126
127nsresult
128MemoryFlusher::Create(MemoryFlusher** aResult, nsMemoryImpl* aMemoryImpl)
129{
130    MemoryFlusher* result = new MemoryFlusher(aMemoryImpl);
131    if (! result)
132        return NS_ERROR_OUT_OF_MEMORY;
133
134    do {
135        if ((result->mLock = PR_NewLock()) == nsnull)
136            break;
137       
138        if ((result->mCVar = PR_NewCondVar(result->mLock)) == nsnull)
139            break;
140
141        NS_ADDREF(*aResult = result);
142        return NS_OK;
143    } while (0);
144
145    // Something bad happened if we get here...
146    delete result;
147    return NS_ERROR_OUT_OF_MEMORY;
148}
149
150NS_IMPL_THREADSAFE_ISUPPORTS1(MemoryFlusher, nsIRunnable)
151
152NS_IMETHODIMP
153MemoryFlusher::Run()
154{
155    nsresult rv;
156
157    mRunning = PR_TRUE;
158
159    while (1) {
160        PRStatus status;
161
162        {
163            nsAutoLock l(mLock);
164            if (! mRunning) {
165                rv = NS_OK;
166                break;
167            }
168
169            status = PR_WaitCondVar(mCVar, mTimeout);
170        }
171
172        if (status != PR_SUCCESS) {
173            rv = NS_ERROR_FAILURE;
174            break;
175        }
176
177        if (! mRunning) {
178            rv = NS_OK;
179            break;
180        }
181
182        PRBool isLowMemory;
183        rv = mMemoryImpl->IsLowMemory(&isLowMemory);
184        if (NS_FAILED(rv))
185            break;
186
187#ifdef NS_TEST_MEMORY_FLUSHER
188        // Fire the flusher *every* time
189        isLowMemory = PR_TRUE;
190#endif
191
192        if (isLowMemory) {
193            mMemoryImpl->FlushMemory(NS_LITERAL_STRING("low-memory").get(), PR_FALSE);
194        }
195    }
196
197    mRunning = PR_FALSE;
198
199    return rv;
200}
201
202
203nsresult
204MemoryFlusher::Stop()
205{
206    if (mRunning) {
207        nsAutoLock l(mLock);
208        mRunning = PR_FALSE;
209        PR_NotifyCondVar(mCVar);
210    }
211
212    return NS_OK;
213}
214
215//----------------------------------------------------------------------
216
217nsMemoryImpl* gMemory = nsnull;
218
219NS_IMPL_THREADSAFE_ISUPPORTS1(nsMemoryImpl, nsIMemory)
220
221NS_METHOD
222nsMemoryImpl::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
223{
224    NS_ENSURE_ARG_POINTER(aInstancePtr);
225    NS_ENSURE_PROPER_AGGREGATION(outer, aIID);
226    if (gMemory && NS_SUCCEEDED(gMemory->QueryInterface(aIID, aInstancePtr)))
227        return NS_OK;
228
229    nsMemoryImpl* mm = new nsMemoryImpl();
230    if (mm == NULL)
231        return NS_ERROR_OUT_OF_MEMORY;
232
233    nsresult rv;
234
235    do {
236        rv = mm->QueryInterface(aIID, aInstancePtr);
237        if (NS_FAILED(rv))
238            break;
239
240        rv = NS_ERROR_OUT_OF_MEMORY;
241
242        mm->mFlushLock = PR_NewLock();
243        if (! mm->mFlushLock)
244            break;
245
246        rv = NS_OK;
247    } while (0);
248
249    if (NS_FAILED(rv))
250        delete mm;
251
252    return rv;
253}
254
255
256nsMemoryImpl::nsMemoryImpl()
257    : mFlusher(nsnull),
258      mFlushLock(nsnull),
259      mIsFlushing(PR_FALSE)
260{
261}
262
263nsMemoryImpl::~nsMemoryImpl()
264{
265    if (mFlushLock)
266        PR_DestroyLock(mFlushLock);
267}
268
269////////////////////////////////////////////////////////////////////////////////
270// Define NS_OUT_OF_MEMORY_TESTER if you want to force memory failures
271
272#ifdef DEBUG_xwarren
273#define NS_OUT_OF_MEMORY_TESTER
274#endif
275
276#ifdef NS_OUT_OF_MEMORY_TESTER
277
278// flush memory one in this number of times:
279#define NS_FLUSH_FREQUENCY        100000
280
281// fail allocation one in this number of flushes:
282#define NS_FAIL_FREQUENCY         10
283
284PRUint32 gFlushFreq = 0;
285PRUint32 gFailFreq = 0;
286
287static void*
288mallocator(PRSize size, PRUint32& counter, PRUint32 max)
289{
290    if (counter++ >= max) {
291        counter = 0;
292        NS_ASSERTION(0, "about to fail allocation... watch out");
293        return nsnull;
294    }
295    return PR_Malloc(size);
296}
297
298static void*
299reallocator(void* ptr, PRSize size, PRUint32& counter, PRUint32 max)
300{
301    if (counter++ >= max) {
302        counter = 0;
303        NS_ASSERTION(0, "about to fail reallocation... watch out");
304        return nsnull;
305    }
306    return PR_Realloc(ptr, size);
307}
308
309#define MALLOC1(s)       mallocator(s, gFlushFreq, NS_FLUSH_FREQUENCY)
310#define REALLOC1(p, s)   reallocator(p, s, gFlushFreq, NS_FLUSH_FREQUENCY)
311
312#else
313
314#define MALLOC1(s)       PR_Malloc(s)
315#define REALLOC1(p, s)   PR_Realloc(p, s)
316
317#endif // NS_OUT_OF_MEMORY_TESTER
318
319////////////////////////////////////////////////////////////////////////////////
320
321NS_IMETHODIMP_(void *)
322nsMemoryImpl::Alloc(PRSize size)
323{
324    NS_ASSERTION(size, "nsMemoryImpl::Alloc of 0");
325    void* result = MALLOC1(size);
326    if (! result) {
327        // Request an asynchronous flush
328        FlushMemory(NS_LITERAL_STRING("alloc-failure").get(), PR_FALSE);
329    }
330    return result;
331}
332
333NS_IMETHODIMP_(void *)
334nsMemoryImpl::Realloc(void * ptr, PRSize size)
335{
336    void* result = REALLOC1(ptr, size);
337    if (! result) {
338        // Request an asynchronous flush
339        FlushMemory(NS_LITERAL_STRING("alloc-failure").get(), PR_FALSE);
340    }
341    return result;
342}
343
344NS_IMETHODIMP_(void)
345nsMemoryImpl::Free(void * ptr)
346{
347    PR_Free(ptr);
348}
349
350NS_IMETHODIMP
351nsMemoryImpl::HeapMinimize(PRBool aImmediate)
352{
353    return FlushMemory(NS_LITERAL_STRING("heap-minimize").get(), aImmediate);
354}
355
356NS_IMETHODIMP
357nsMemoryImpl::IsLowMemory(PRBool *result)
358{
359#if defined(XP_WIN)
360    MEMORYSTATUS stat;
361    GlobalMemoryStatus(&stat);
362    *result = ((float)stat.dwAvailPageFile / stat.dwTotalPageFile) < 0.1;
363#elif defined(XP_MAC)
364
365  const long kReserveHeapFreeSpace = (256 * 1024);
366  const long kReserveHeapContigSpace = (128 * 1024);
367
368  long totalSpace, contiguousSpace;
369  // this call measures how much memory would be available if the OS
370  // purged. Despite the name, it does not purge (that happens
371  // automatically when heap space is low).
372  ::PurgeSpace(&totalSpace, &contiguousSpace);
373  if (totalSpace < kReserveHeapFreeSpace || contiguousSpace < kReserveHeapContigSpace)
374  {
375    NS_WARNING("Found that heap mem is low");
376    *result = PR_TRUE;
377    return NS_OK;
378  }
379
380  // see how much temp mem is available (since our allocators allocate 1Mb chunks
381  // in temp mem. We don't use TempMaxMem() (to get contig space) here, because it
382  // compacts the application heap, so can be slow.
383  const long kReserveTempFreeSpace = (2 * 1024 * 1024);     // 2Mb 
384  long  totalTempSpace = ::TempFreeMem(); 
385  if (totalTempSpace < kReserveTempFreeSpace)
386  {
387    NS_WARNING("Found that temp mem is low");
388    *result = PR_TRUE;
389    return NS_OK;
390  } 
391
392  *result = PR_FALSE;
393 
394#else
395    *result = PR_FALSE;
396#endif
397    return NS_OK;
398}
399
400nsresult
401nsMemoryImpl::FlushMemory(const PRUnichar* aReason, PRBool aImmediate)
402{
403    nsresult rv;
404
405    if (aImmediate) {
406        // They've asked us to run the flusher *immediately*. We've
407        // got to be on the UI main thread for us to be able to do
408        // that...are we?
409        PRBool isOnUIThread = PR_FALSE;
410
411        nsCOMPtr<nsIThread> main;
412        rv = nsIThread::GetMainThread(getter_AddRefs(main));
413        if (NS_SUCCEEDED(rv)) {
414            nsCOMPtr<nsIThread> current;
415            rv = nsIThread::GetCurrent(getter_AddRefs(current));
416            if (NS_SUCCEEDED(rv)) {
417                if (current == main)
418                    isOnUIThread = PR_TRUE;
419            }
420        }
421
422        if (! isOnUIThread) {
423            NS_ERROR("can't synchronously flush memory: not on UI thread");
424            return NS_ERROR_FAILURE;
425        }
426    }
427
428    {
429        // Are we already flushing?
430        nsAutoLock l(mFlushLock);
431        if (mIsFlushing)
432            return NS_OK;
433
434        // Well, we are now!
435        mIsFlushing = PR_TRUE;
436    }
437
438    // Run the flushers immediately if we can; otherwise, proxy to the
439    // UI thread an run 'em asynchronously.
440    if (aImmediate) {
441        rv = RunFlushers(this, aReason);
442    }
443    else {
444        nsCOMPtr<nsIEventQueueService> eqs = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
445        if (eqs) {
446            nsCOMPtr<nsIEventQueue> eq;
447            rv = eqs->GetThreadEventQueue(NS_UI_THREAD, getter_AddRefs(eq));
448            if (NS_SUCCEEDED(rv)) {
449                PL_InitEvent(&mFlushEvent.mEvent, this, HandleFlushEvent, DestroyFlushEvent);
450                mFlushEvent.mReason = aReason;
451
452                rv = eq->PostEvent(NS_REINTERPRET_CAST(PLEvent*, &mFlushEvent));
453            }
454        }
455    }
456
457    return rv;
458}
459
460nsresult
461nsMemoryImpl::RunFlushers(nsMemoryImpl* aSelf, const PRUnichar* aReason)
462{
463    nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
464    if (os) {
465        os->NotifyObservers(aSelf, "memory-pressure", aReason);
466    }
467
468    {
469        // Done flushing
470        nsAutoLock l(aSelf->mFlushLock);
471        aSelf->mIsFlushing = PR_FALSE;
472    }
473
474    return NS_OK;
475}
476
477void*
478nsMemoryImpl::HandleFlushEvent(PLEvent* aEvent)
479{
480    nsMemoryImpl* self = NS_STATIC_CAST(nsMemoryImpl*, PL_GetEventOwner(aEvent));
481    FlushEvent* event = NS_REINTERPRET_CAST(FlushEvent*, aEvent);
482
483    RunFlushers(self, event->mReason);
484    return 0;
485}
486
487void
488nsMemoryImpl::DestroyFlushEvent(PLEvent* aEvent)
489{
490    // no-op, since mEvent is a member of nsMemoryImpl
491}
492
493static void
494EnsureGlobalMemoryService()
495{
496    if (gMemory) return;
497    nsresult rv = nsMemoryImpl::Create(nsnull, NS_GET_IID(nsIMemory), (void**)&gMemory);
498    NS_ASSERTION(NS_SUCCEEDED(rv), "nsMemoryImpl::Create failed");
499    NS_ASSERTION(gMemory, "improper xpcom initialization");
500}
501
502nsresult
503nsMemoryImpl::Startup()
504{
505    EnsureGlobalMemoryService();
506    if (! gMemory)
507        return NS_ERROR_FAILURE;
508
509#ifdef NS_MEMORY_FLUSHER_THREAD
510    nsresult rv;
511
512    // Create and start a memory flusher thread
513    rv = MemoryFlusher::Create(&gMemory->mFlusher, gMemory);
514    if (NS_FAILED(rv)) return rv;
515
516    rv = NS_NewThread(getter_AddRefs(gMemory->mFlusherThread),
517                      gMemory->mFlusher,
518                      0, /* XXX use default stack size? */
519                      PR_JOINABLE_THREAD);
520
521    if (NS_FAILED(rv)) return rv;
522#endif
523
524    return NS_OK;
525}
526
527nsresult
528nsMemoryImpl::Shutdown()
529{
530    if (gMemory) {
531#ifdef NS_MEMORY_FLUSHER_THREAD
532        if (gMemory->mFlusher) {
533            // Stop the runnable...
534            gMemory->mFlusher->Stop();
535            NS_RELEASE(gMemory->mFlusher);
536
537            // ...and wait for the thread to exit
538            if (gMemory->mFlusherThread)
539                gMemory->mFlusherThread->Join();
540        }
541#endif
542
543        NS_RELEASE(gMemory);
544        gMemory = nsnull;
545    }
546
547    return NS_OK;
548}
Note: See TracBrowser for help on using the repository browser.