1215 lines
28 KiB
C
Executable File
1215 lines
28 KiB
C
Executable File
/*
|
|
* @file ejsGarbage.c
|
|
* @brief EJS Garbage collector.
|
|
* @overview This implements a generational mark and sweep collection scheme.
|
|
*/
|
|
/********************************* Copyright **********************************/
|
|
/*
|
|
* @copy default
|
|
*
|
|
* Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved.
|
|
*
|
|
* This software is distributed under commercial and open source licenses.
|
|
* You may use the GPL open source license described below or you may acquire
|
|
* a commercial license from Mbedthis Software. You agree to be fully bound
|
|
* by the terms of either license. Consult the LICENSE.TXT distributed with
|
|
* this software for full details.
|
|
*
|
|
* This software is open source; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See the GNU General Public License for more
|
|
* details at: http://www.mbedthis.com/downloads/gplLicense.html
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* This GPL license does NOT permit incorporating this software into
|
|
* proprietary programs. If you are unable to comply with the GPL, you must
|
|
* acquire a commercial license to use this software. Commercial licenses
|
|
* for this software and support services are available from Mbedthis
|
|
* Software at http://www.mbedthis.com
|
|
*
|
|
* @end
|
|
*/
|
|
/********************************** Includes **********************************/
|
|
|
|
#include "ejs.h"
|
|
|
|
#if BLD_FEATURE_EJS
|
|
|
|
/****************************** Forward Declarations **************************/
|
|
|
|
static void mark(Ejs *ep);
|
|
static void markObjByVar(Ejs *ep, EjsVar *op);
|
|
static void markObj(EjsObj *obj);
|
|
static void markPerm(Ejs *ep, uint gen);
|
|
static int sweep(Ejs *ep, uint gen);
|
|
static EjsGCLink *ejsAlloc(EJS_LOC_DEC(ep, loc), int slabIndex);
|
|
static void ejsGracefulDegrade(Ejs *ep);
|
|
static void resetMarks(Ejs *ep, EjsSlab *slab);
|
|
|
|
#if FUTURE
|
|
static void ageGenerations(Ejs *ep);
|
|
#endif
|
|
|
|
#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
|
|
uint breakAddr;
|
|
#endif
|
|
|
|
/************************************* Code ***********************************/
|
|
|
|
void ejsGCInit(Ejs *ep, int objInc, int propInc, int varInc, int strInc)
|
|
{
|
|
EjsSlab *slab;
|
|
|
|
if (ep->service && ep->service->globalClass) {
|
|
ep->service->globalClass->objectState->gcMarked = 1;
|
|
}
|
|
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
slab->allocIncrement = objInc;
|
|
slab->size = EJS_ALLOC_ALIGN(sizeof(EjsObj));
|
|
|
|
slab = &ep->slabs[EJS_SLAB_PROPERTY];
|
|
slab->allocIncrement = propInc;
|
|
slab->size = EJS_ALLOC_ALIGN(sizeof(EjsProperty));
|
|
|
|
slab = &ep->slabs[EJS_SLAB_VAR];
|
|
slab->allocIncrement = varInc;
|
|
slab->size = EJS_ALLOC_ALIGN(sizeof(EjsVar));
|
|
|
|
/*
|
|
* Initialize GC.
|
|
* Enable GC both idle and demand collections.
|
|
* Set no limits and garbage collect if the slabs are
|
|
* empty and we have used more than the THRESHOLD of ram.
|
|
*/
|
|
ep->gc.debugLevel = 0;
|
|
ep->gc.enable = 1;
|
|
ep->gc.enableIdleCollect = 1;
|
|
ep->gc.enableDemandCollect = 1;
|
|
ep->gc.workQuota = EJS_GC_WORK_QUOTA;
|
|
ep->gc.maxMemory = 0;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
|
|
void ejsPrintAllocReport(Ejs *ep, bool printLeakReport)
|
|
{
|
|
EjsSlab *slab;
|
|
char *name;
|
|
int slabIndex, isObj;
|
|
|
|
for (slabIndex = 0; slabIndex < EJS_SLAB_MAX; slabIndex++) {
|
|
slab = &ep->slabs[slabIndex];
|
|
if (slabIndex == EJS_SLAB_VAR) {
|
|
name = "var";
|
|
} else if (slabIndex == EJS_SLAB_PROPERTY) {
|
|
name = "prop";
|
|
} else {
|
|
name = "obj";
|
|
}
|
|
mprLog(ep, 0, " ");
|
|
mprLog(ep, 0, " GC \"%s\" local slab", name);
|
|
mprLog(ep, 0, " Total blocks %14d",
|
|
slab->allocCount + slab->freeCount);
|
|
mprLog(ep, 0, " Block size %14d", slab->size);
|
|
mprLog(ep, 0, " Slab RAM allocated %14d",
|
|
(slab->allocCount + slab->freeCount) * slab->size);
|
|
mprLog(ep, 0, " Slab RAM in use %14d",
|
|
slab->allocCount * slab->size);
|
|
mprLog(ep, 0, " Blocks in use %14d", slab->allocCount);
|
|
mprLog(ep, 0, " Free blocks %14d", slab->freeCount);
|
|
mprLog(ep, 0, " Peak allocated %14d", slab->peakAllocated);
|
|
mprLog(ep, 0, " Peak free %14d", slab->peakFree);
|
|
mprLog(ep, 0, " Total allocations %14d", slab->totalAlloc);
|
|
mprLog(ep, 0, " Total blocks reclaimed %14d", slab->totalReclaimed);
|
|
mprLog(ep, 0, " Total sweeps %14d", slab->totalSweeps);
|
|
mprLog(ep, 0, " Allocation inc %14d", slab->allocIncrement);
|
|
}
|
|
|
|
mprLog(ep, 0, " ");
|
|
mprLog(ep, 0, " Total EJS memory in use %10d", ejsGetUsedMemory(ep));
|
|
mprLog(ep, 0, " Total EJS memory allocated %10d",
|
|
ejsGetAllocatedMemory(ep));
|
|
|
|
if (printLeakReport) {
|
|
mprLog(ep, 0, " ");
|
|
for (slabIndex = 0; slabIndex < EJS_SLAB_MAX; slabIndex++) {
|
|
int size;
|
|
|
|
slab = &ep->slabs[slabIndex];
|
|
|
|
isObj = 0;
|
|
mprLog(ep, 0, " ");
|
|
if (slabIndex == EJS_SLAB_VAR) {
|
|
name = "var";
|
|
size = sizeof(EjsVar);
|
|
} else if (slabIndex == EJS_SLAB_PROPERTY) {
|
|
name = "prop";
|
|
size = sizeof(EjsProperty);
|
|
} else {
|
|
name = "obj";
|
|
size = sizeof(EjsObj);
|
|
isObj++;
|
|
}
|
|
#if BLD_FEATURE_ALLOC_LEAK_TRACK
|
|
{
|
|
EjsGCLink *lp;
|
|
EjsObj *obj;
|
|
int count;
|
|
|
|
mprLog(ep, 0, "EJS Leak Report for \"%s\"", name);
|
|
count = 0;
|
|
|
|
for (lp = slab->allocList[0].next; lp; lp = lp->next) {
|
|
mprLog(ep, 0, " %-20s %10d", lp->allocatedBy, size);
|
|
if (isObj) {
|
|
obj = (EjsObj*) lp;
|
|
mprLog(ep, 0, " %-20s %10d %s %s",
|
|
lp->allocatedBy, size,
|
|
obj->permanent ? "permanent" : "",
|
|
obj->alive ? "alive" : ""
|
|
);
|
|
} else {
|
|
mprLog(ep, 0, " %-20s %10d", lp->allocatedBy,
|
|
size);
|
|
}
|
|
count++;
|
|
}
|
|
mprLog(ep, 0, " Total blocks %14d", count);
|
|
}
|
|
#endif
|
|
}
|
|
mprLog(ep, 0, " ");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
/******************************************************************************/
|
|
/*
|
|
* Slab allocator
|
|
*/
|
|
|
|
static EjsGCLink *ejsAlloc(EJS_LOC_DEC(ep, loc), int slabIndex)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsGCLink *block;
|
|
EjsGC *gc;
|
|
uint allocatedMemory;
|
|
int i;
|
|
|
|
mprStackCheck(ep);
|
|
|
|
if (slabIndex < 0 || slabIndex >= EJS_SLAB_MAX) {
|
|
mprAssert(0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* See if the slab has some free blocks
|
|
*/
|
|
slab = &ep->slabs[slabIndex];
|
|
if ((block = slab->freeList.next) == 0) {
|
|
|
|
allocatedMemory = ejsGetAllocatedMemory(ep);
|
|
gc = &ep->gc;
|
|
|
|
/*
|
|
* No blocks available. If demand collection is enabled, try
|
|
* to garbage collect first. We collect if we have done a good
|
|
* work quota or we are over the max memory limit.
|
|
*/
|
|
if (slabIndex != EJS_SLAB_VAR &&
|
|
ep->gc.enable && ep->gc.enableDemandCollect) {
|
|
if ((ep->gc.workDone > ep->gc.workQuota) ||
|
|
(gc->maxMemory > 0 && allocatedMemory > gc->maxMemory)) {
|
|
|
|
#if DEBUG_USE_ONLY
|
|
if (ep->gc.debugLevel > 0) {
|
|
mprLog(ep, 0, "Need GC, EJS RAM %d, MPR RAM %d\n",
|
|
allocatedMemory, mprGetAllocatedMemory(ep));
|
|
if (ep->gc.debugLevel > 4) {
|
|
ejsPrintAllocReport(ep, 0);
|
|
}
|
|
}
|
|
#endif
|
|
if (ejsCollectGarbage(ep, slabIndex) == 0) {
|
|
block = slab->freeList.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (block == 0) {
|
|
if (gc->maxMemory > 0 && allocatedMemory > gc->maxMemory) {
|
|
/*
|
|
* We are above the max memory limit. We will fail this
|
|
* memory allocation, but allow subsequent allocations to
|
|
* permit error recovery. We gracefully degrade by setting
|
|
* slab chunk sizes to 1. This minimizes real memory
|
|
* consumption. This allows us to create
|
|
* an exception block to be created by upper layers.
|
|
*/
|
|
if (! gc->degraded) {
|
|
ejsGracefulDegrade(ep);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Still non available, so allocate more memory for a set of blocks
|
|
* OPT -- should bypass mprAlloc. Need mprMalloc.
|
|
*/
|
|
block = mprAlloc(ep->slabAllocContext,
|
|
slab->size * slab->allocIncrement);
|
|
if (block == 0) {
|
|
/*
|
|
* Now we're in trouble. We should really never get here
|
|
* as the graceful degrade will have signaled a memory
|
|
* allocation failure.
|
|
*/
|
|
mprAssert(block != 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Chain all the blocks together onto the slab free list
|
|
*/
|
|
for (i = slab->allocIncrement - 1; i >= 0; i--) {
|
|
block->next = slab->freeList.next;
|
|
#if BLD_DEBUG
|
|
block->magic = EJS_MAGIC_FREE;
|
|
#endif
|
|
slab->freeList.next = block;
|
|
block = (EjsGCLink*) ((char*) block + slab->size);
|
|
}
|
|
|
|
block = slab->freeList.next;
|
|
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
slab->freeCount += slab->allocIncrement;
|
|
if (slab->freeCount > slab->peakFree) {
|
|
slab->peakFree = slab->freeCount;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We use block to point to the user data in the block. We only
|
|
* store the magic number (if debug). No other data is stored in the
|
|
* user block.
|
|
*/
|
|
#if BLD_DEBUG
|
|
mprAssert(block->magic == EJS_MAGIC_FREE);
|
|
#endif
|
|
|
|
/*
|
|
* Remove from the free list
|
|
*/
|
|
slab->freeList.next = block->next;
|
|
|
|
/*
|
|
* Zero block
|
|
*/
|
|
memset(block, 0, slab->size);
|
|
|
|
#if BLD_DEBUG
|
|
block->magic = EJS_MAGIC;
|
|
#endif
|
|
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
slab->totalAlloc++;
|
|
if (++slab->allocCount > slab->peakAllocated) {
|
|
slab->peakAllocated = slab->allocCount;
|
|
}
|
|
slab->freeCount--;
|
|
#endif
|
|
|
|
#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
|
|
if ((uint) block == breakAddr) {
|
|
mprBreakpoint(MPR_LOC, "Watched Block");
|
|
}
|
|
#endif
|
|
return block;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
EjsObj *ejsAllocObj(EJS_LOC_DEC(ep, loc))
|
|
{
|
|
EjsObj *obj;
|
|
EjsSlab *slab;
|
|
|
|
obj = (EjsObj*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_OBJ);
|
|
|
|
/*
|
|
* Add to the allocated block list for the New generation.
|
|
*/
|
|
if (obj) {
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
obj->gc.next = slab->allocList[EJS_GEN_NEW].next;
|
|
|
|
#if BLD_FEATURE_ALLOC_LEAK_TRACK
|
|
obj->gc.allocatedBy = loc;
|
|
#endif
|
|
|
|
obj->ejs = ep;
|
|
slab->allocList[EJS_GEN_NEW].next = (EjsGCLink*) obj;
|
|
|
|
ep->gc.workDone++;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
EjsProperty *ejsAllocProperty(EJS_LOC_DEC(ep, loc))
|
|
{
|
|
EjsProperty *prop;
|
|
|
|
prop = (EjsProperty*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_PROPERTY);
|
|
mprAssert(prop);
|
|
|
|
if (prop) {
|
|
prop->var.type = EJS_TYPE_NULL;
|
|
prop->var.isProperty = 1;
|
|
#if BLD_FEATURE_ALLOC_LEAK_TRACK
|
|
prop->var.gc.allocatedBy = loc;
|
|
#endif
|
|
}
|
|
return prop;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
EjsVar *ejsAllocVar(EJS_LOC_DEC(ep, loc))
|
|
{
|
|
EjsVar *vp;
|
|
|
|
vp = (EjsVar*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_VAR);
|
|
mprAssert(vp);
|
|
|
|
if (vp) {
|
|
#if BLD_FEATURE_ALLOC_LEAK_TRACK
|
|
EjsSlab *slab;
|
|
vp->gc.allocatedBy = loc;
|
|
slab = &ep->slabs[EJS_SLAB_VAR];
|
|
vp->gc.next = slab->allocList[EJS_GEN_NEW].next;
|
|
slab->allocList[EJS_GEN_NEW].next = (EjsGCLink*) vp;
|
|
#endif
|
|
#if BLD_DEBUG
|
|
vp->propertyName = 0;
|
|
#endif
|
|
}
|
|
return vp;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Return the block back to the relevant slab
|
|
*/
|
|
|
|
void ejsFree(Ejs *ep, void *ptr, int slabIndex)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsGCLink *block;
|
|
|
|
mprAssert(ep);
|
|
mprAssert(ptr);
|
|
|
|
if (slabIndex < 0 || slabIndex >= EJS_SLAB_MAX) {
|
|
mprAssert(slabIndex >= 0 && slabIndex < EJS_SLAB_MAX);
|
|
return;
|
|
}
|
|
slab = &ep->slabs[slabIndex];
|
|
|
|
#if BLD_FEATURE_ALLOC_LEAK_TRACK
|
|
if (slabIndex == EJS_SLAB_VAR) {
|
|
EjsVar *vp, *np, *prev;
|
|
|
|
/*
|
|
* Remove the block rom the alloc list. WARNING: this is slow
|
|
* and should not be used in production code.
|
|
*/
|
|
vp = (EjsVar*) ptr;
|
|
prev = 0;
|
|
for (np = (EjsVar*) slab->allocList[0].next; np;
|
|
np = (EjsVar*) np->gc.next) {
|
|
if (vp == np) {
|
|
if (prev) {
|
|
prev->gc.next = (EjsGCLink*) np->gc.next;
|
|
} else {
|
|
slab->allocList[0].next = (EjsGCLink*) np->gc.next;
|
|
}
|
|
break;
|
|
}
|
|
prev = np;
|
|
}
|
|
if (np == 0) {
|
|
mprAssert(0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Insert into the free list. Only use the next ptr
|
|
*/
|
|
block = (EjsGCLink*) ptr;
|
|
|
|
#if BLD_DEBUG
|
|
#if !BREW || BREW_SIMULATOR
|
|
if ((uint) block == breakAddr) {
|
|
mprBreakpoint(MPR_LOC, "Watched Block");
|
|
}
|
|
#endif
|
|
mprAssert(block->magic == EJS_MAGIC);
|
|
block->magic = EJS_MAGIC_FREE;
|
|
#endif
|
|
|
|
block->next = slab->freeList.next;
|
|
slab->freeList.next = block;
|
|
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
slab->allocCount--;
|
|
if (++slab->freeCount >= slab->peakFree) {
|
|
slab->peakFree = slab->freeCount;
|
|
}
|
|
slab->totalReclaimed++;
|
|
if (slabIndex != 2) {
|
|
slabIndex = slabIndex;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Mark an object as being in-use. Traverse all properties for referenced
|
|
* objects and base classes.
|
|
*/
|
|
|
|
static void markObjByVar(Ejs *ep, EjsVar *obj)
|
|
{
|
|
EjsProperty *pp;
|
|
EjsVar *vp, *baseClass;
|
|
|
|
mprAssert(ep);
|
|
mprAssert(obj);
|
|
|
|
obj->objectState->gcMarked = 1;
|
|
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 3) {
|
|
int indent = min(ep->gc.gcIndent * 2, 32);
|
|
mprLog(ep, 0, "%.*s %-24s %.*s 0x%08X",
|
|
indent, " ",
|
|
obj->propertyName,
|
|
32 - indent, "................................ ",
|
|
(uint) obj->objectState);
|
|
ep->gc.gcIndent++;
|
|
}
|
|
ep->gc.objectsInUse++;
|
|
#endif
|
|
|
|
/*
|
|
* Traverse all referenced objects
|
|
* OPT -- optimize by directly accessing the object links and not using
|
|
* ejsGetFirst/NextProperty. Then just examine objects
|
|
* OPT -- first property in global is global. Should optimize this.
|
|
*/
|
|
pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL);
|
|
while (pp) {
|
|
vp = ejsGetVarPtr(pp);
|
|
if (vp->type == EJS_TYPE_OBJECT) {
|
|
if (!vp->objectState->gcMarked) {
|
|
#if FUTURE
|
|
/*
|
|
* OPT -- we can use the dirty bit on objects to avoid
|
|
* visiting permanent objects that are clean. If so, don't
|
|
* forget the else case below.
|
|
*/
|
|
obj = vp->objectState;
|
|
if ((!obj->alive && !obj->permanent) || obj->dirty)
|
|
#endif
|
|
markObjByVar(ep, vp);
|
|
}
|
|
|
|
} else {
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 3) {
|
|
int indent = min(ep->gc.gcIndent * 2, 32);
|
|
mprLog(ep, 0, "%.*s %-24s %.*s %s",
|
|
indent, " ",
|
|
vp->propertyName,
|
|
32 - indent, "................................ ",
|
|
ejsGetVarTypeAsString(vp));
|
|
}
|
|
ep->gc.propertiesInUse++;
|
|
#endif
|
|
}
|
|
pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
|
|
}
|
|
|
|
/*
|
|
* Traverse the base class
|
|
*/
|
|
baseClass = obj->objectState->baseClass;
|
|
if (baseClass) {
|
|
mprAssert(baseClass->type == EJS_TYPE_OBJECT);
|
|
mprAssert(baseClass->objectState);
|
|
if (baseClass->objectState) {
|
|
if (! baseClass->objectState->gcMarked) {
|
|
markObjByVar(ep, baseClass);
|
|
}
|
|
}
|
|
}
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 3) {
|
|
ep->gc.gcIndent--;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Mark phase. Examine all variable frames and the return result.
|
|
*/
|
|
|
|
static void mark(Ejs *ep)
|
|
{
|
|
EjsVar *vp;
|
|
int i;
|
|
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 3) {
|
|
mprLog(ep, 0, " ");
|
|
mprLog(ep, 0, "GC: Marked Blocks:");
|
|
}
|
|
#endif
|
|
|
|
if (ep->frames) {
|
|
for (i = 0; i < mprGetItemCount(ep->frames); i++) {
|
|
|
|
vp = (EjsVar*) mprGetItem(ep->frames, i);
|
|
mprAssert(vp->type == EJS_TYPE_OBJECT);
|
|
|
|
if (! vp->objectState->gcMarked) {
|
|
markObjByVar(ep, vp);
|
|
}
|
|
}
|
|
}
|
|
|
|
vp = ep->result;
|
|
if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
|
|
markObjByVar(ep, vp);
|
|
}
|
|
|
|
vp = ep->currentObj;
|
|
if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
|
|
markObjByVar(ep, vp);
|
|
}
|
|
|
|
vp = ejsGetVarPtr(ep->currentProperty);
|
|
if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
|
|
markObjByVar(ep, vp);
|
|
}
|
|
|
|
/*
|
|
* OPT -- we could mark master as "mark permanent" somehow and
|
|
* then we would not need to walk the master objects.
|
|
*/
|
|
if (ep->slabAllocContext == ep->service->master) {
|
|
if (ep->service->master->global) {
|
|
markObjByVar(ep, ep->service->master->global);
|
|
}
|
|
}
|
|
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 3) {
|
|
mprLog(ep, 0, " ");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
#if UNUSED
|
|
|
|
static void resetMark(EjsVar *obj)
|
|
{
|
|
EjsProperty *pp;
|
|
EjsVar *vp, *baseClass;
|
|
|
|
obj->objectState->gcMarked = 0;
|
|
obj->objectState->visited = 1;
|
|
|
|
pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL);
|
|
while (pp) {
|
|
vp = ejsGetVarPtr(pp);
|
|
if (vp->type == EJS_TYPE_OBJECT && !vp->objectState->visited) {
|
|
resetMark(vp);
|
|
}
|
|
pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
|
|
}
|
|
|
|
baseClass = obj->objectState->baseClass;
|
|
if (baseClass) {
|
|
mprAssert(baseClass->type == EJS_TYPE_OBJECT);
|
|
mprAssert(baseClass->objectState);
|
|
if (baseClass->objectState) {
|
|
if (! baseClass->objectState->visited) {
|
|
resetMark(baseClass);
|
|
}
|
|
}
|
|
}
|
|
obj->objectState->visited = 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Mark phase. Examine all variable frames and the return result.
|
|
*/
|
|
|
|
static void resetAllMarks(Ejs *ep)
|
|
{
|
|
EjsVar *vp;
|
|
int i;
|
|
|
|
for (i = 0; i < mprGetItemCount(ep->frames); i++) {
|
|
vp = (EjsVar*) mprGetItem(ep->frames, i);
|
|
resetMark(vp);
|
|
}
|
|
|
|
if (ep->result && ep->result->type == EJS_TYPE_OBJECT &&
|
|
! ep->result->objectState->gcMarked) {
|
|
resetMark(ep->result);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
/******************************************************************************/
|
|
/*
|
|
* Sweep up the garbage
|
|
*/
|
|
|
|
static void resetMarks(Ejs *ep, EjsSlab *slab)
|
|
{
|
|
EjsVar *vp;
|
|
EjsObj *obj;
|
|
int gen, i;
|
|
|
|
for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
|
|
obj = (EjsObj*) slab->allocList[gen].next;
|
|
for (; obj; obj = (EjsObj*) obj->gc.next) {
|
|
obj->gcMarked = 0;
|
|
obj->visited = 0;
|
|
}
|
|
}
|
|
|
|
if (ep->frames) {
|
|
for (i = 0; i < mprGetItemCount(ep->frames); i++) {
|
|
|
|
vp = (EjsVar*) mprGetItem(ep->frames, i);
|
|
mprAssert(vp->type == EJS_TYPE_OBJECT);
|
|
|
|
vp->objectState->gcMarked = 0;
|
|
vp->objectState->visited = 0;
|
|
}
|
|
}
|
|
|
|
if (ep->result && ep->result->type == EJS_TYPE_OBJECT) {
|
|
ep->result->objectState->gcMarked = 0;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Mark all permanent and non-alive objects
|
|
*/
|
|
|
|
static void markPerm(Ejs *ep, uint gen)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsObj *obj;
|
|
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
|
|
for (obj = (EjsObj*) slab->allocList[gen].next; obj; ) {
|
|
|
|
if (! obj->gcMarked) {
|
|
if (!obj->alive || obj->permanent) {
|
|
markObj(obj);
|
|
}
|
|
}
|
|
obj = (EjsObj*) obj->gc.next;
|
|
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void markObj(EjsObj *obj)
|
|
{
|
|
EjsProperty *pp;
|
|
EjsPropLink *lp, *head;
|
|
EjsObj *op;
|
|
|
|
mprAssert(obj);
|
|
|
|
obj->gcMarked = 1;
|
|
|
|
head = &obj->link;
|
|
for (lp = head->next; lp != head; lp = lp->next) {
|
|
|
|
pp = ejsGetPropertyFromLink(lp);
|
|
|
|
if (pp->var.type == EJS_TYPE_OBJECT) {
|
|
op = pp->var.objectState;
|
|
if (op != 0 && !op->gcMarked) {
|
|
markObj(op);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Sweep up the garbage. Return the number of objects freed.
|
|
*/
|
|
|
|
static int sweep(Ejs *ep, uint gen)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsObj *obj, *next, *prev;
|
|
int count;
|
|
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
|
|
/*
|
|
* Examine allocated objects in the specified generation (only).
|
|
* NOTE: we only sweep object allocated to this interpreter and so
|
|
* we do not sweep any permanent objects in the default interpreter.
|
|
*/
|
|
prev = 0;
|
|
count = 0;
|
|
for (obj = (EjsObj*) slab->allocList[gen].next; obj; obj = next) {
|
|
|
|
next = (EjsObj*) obj->gc.next;
|
|
|
|
#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
|
|
if ((uint) obj == breakAddr) {
|
|
mprBreakpoint(MPR_LOC, "Watched Block");
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If object has not been marked inuse and is not a permanent
|
|
* object, then free it.
|
|
*/
|
|
if (! obj->gcMarked && obj->alive && !obj->permanent) {
|
|
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel >= 2) {
|
|
if (obj->objName) {
|
|
mprLog(ep, 0, "GC: destroy %-18s %10d, %8X",
|
|
obj->objName, (uint) obj, (uint) obj);
|
|
} else {
|
|
mprLog(ep, 0, "GC: destroy UNKNOWN %x", (uint) obj);
|
|
}
|
|
}
|
|
#endif
|
|
if (ejsDestroyObj(ep, obj) < 0) {
|
|
prev = obj;
|
|
obj->gcMarked = 0;
|
|
continue;
|
|
}
|
|
|
|
if (prev) {
|
|
prev->gc.next = (EjsGCLink*) next;
|
|
} else {
|
|
slab->allocList[gen].next = (EjsGCLink*) next;
|
|
}
|
|
count++;
|
|
|
|
} else {
|
|
prev = obj;
|
|
/* Reset for next time */
|
|
obj->gcMarked = 0;
|
|
}
|
|
}
|
|
|
|
if (gen == (EJS_GEN_OLD - 1)) {
|
|
slab->lastRecentBlock = prev;
|
|
}
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
slab->totalSweeps++;
|
|
#endif
|
|
#if BLD_DEBUG
|
|
if (ep->gc.debugLevel > 0) {
|
|
mprLog(ep, 0, "GC: Sweep freed %d objects", count);
|
|
}
|
|
#endif
|
|
return count;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Sweep all variables
|
|
*/
|
|
|
|
void ejsSweepAll(Ejs *ep)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsObj *obj, *next, *prev;
|
|
int gen;
|
|
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
|
|
for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
|
|
prev = 0;
|
|
for (obj = (EjsObj*) slab->allocList[gen].next; obj; obj = next) {
|
|
next = (EjsObj*) obj->gc.next;
|
|
ejsDestroyObj(ep, obj);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
bool ejsObjIsCollectable(EjsVar *vp)
|
|
{
|
|
if (vp == 0 || !ejsVarIsObject(vp)) {
|
|
return 0;
|
|
}
|
|
return (vp->objectState->alive && !vp->objectState->permanent);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
#if FUTURE
|
|
|
|
static void ageGenerations(Ejs *ep)
|
|
{
|
|
EjsSlab *slab;
|
|
EjsGCLink *oldList;
|
|
int gen;
|
|
|
|
slab = &ep->slabs[EJS_SLAB_OBJ];
|
|
|
|
/*
|
|
* Age all blocks. First append all (old - 1) blocks onto the old
|
|
* alloc list
|
|
*/
|
|
oldList = &slab->allocList[EJS_GEN_OLD];
|
|
|
|
if (slab->lastRecentBlock) {
|
|
slab->lastRecentBlock->gc.next = oldList->next;
|
|
oldList->next = (EjsGCLink*) slab->lastRecentBlock;
|
|
}
|
|
|
|
/*
|
|
* Now simply copy all allocation lists up one generation
|
|
*/
|
|
for (gen = EJS_GEN_OLD - 1; gen > 0; gen--) {
|
|
slab->allocList[gen] = slab->allocList[gen - 1];
|
|
}
|
|
slab->allocList[0].next = 0;
|
|
}
|
|
|
|
#endif
|
|
/******************************************************************************/
|
|
/*
|
|
* Collect the garbage. This is a mark and sweep over all possible objects.
|
|
* If an object is not referenced, it and all contained properties will be
|
|
* freed. If a slabIndex is provided, the collection halts when a block is
|
|
* available for allocation on that slab.
|
|
*
|
|
* Return 0 if memory is now available after collecting garbage. Otherwise,
|
|
* return MPR_ERR_MEMORY.
|
|
*/
|
|
|
|
int ejsCollectGarbage(Ejs *ep, int slabIndex)
|
|
{
|
|
EjsGeneration gen;
|
|
|
|
if (ep->flags & EJS_FLAGS_DONT_GC) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Prevent destructors invoking the garbage collector
|
|
*/
|
|
if (ep->gc.collecting) {
|
|
return 0;
|
|
}
|
|
ep->gc.collecting = 1;
|
|
|
|
resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
|
|
|
|
/*
|
|
* Examine each generation of objects starting with the most recent
|
|
* generation. Stop scanning when we have a free block to use.
|
|
*/
|
|
for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
|
|
|
|
if (slabIndex >= 0 && ep->slabs[slabIndex].freeList.next) {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* FUTURE OPT. Should mark objects in new generation and those
|
|
* with a dirty bit set in older generations. Don't need to mark
|
|
* entire heap. But how to keep list of dirty objects.
|
|
*/
|
|
mark(ep);
|
|
markPerm(ep, gen);
|
|
sweep(ep, gen);
|
|
|
|
/* FUTURE - not using generations yet */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* FUTURE -- not using generations yet.
|
|
*
|
|
* ageGenerations(ep);
|
|
*/
|
|
|
|
ep->gc.workDone = 0;
|
|
ep->gc.collecting = 0;
|
|
|
|
return (gen < EJS_GEN_MAX) ? 0 : MPR_ERR_MEMORY;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Should be called when the app has been idle for a little while and when it
|
|
* is likely to be idle a bit longer. Call ejsIsTimeForGC to see if this is
|
|
* true. Return the count of objects collected .
|
|
*/
|
|
|
|
int ejsIncrementalCollectGarbage(Ejs *ep)
|
|
{
|
|
int count;
|
|
|
|
if (ep->gc.collecting) {
|
|
return 0;
|
|
}
|
|
|
|
ep->gc.collecting = 1;
|
|
|
|
resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
|
|
mark(ep);
|
|
|
|
/* Not generational yet */
|
|
count = sweep(ep, EJS_GEN_NEW);
|
|
|
|
ep->gc.collecting = 0;
|
|
ep->gc.workDone = 0;
|
|
|
|
return count;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
#if BLD_DEBUG
|
|
|
|
void ejsDumpObjects(Ejs *ep)
|
|
{
|
|
int oldDebugLevel;
|
|
|
|
mprLog(ep, 0, "Dump of objects in use\n");
|
|
|
|
oldDebugLevel = ep->gc.debugLevel;
|
|
|
|
ep->gc.debugLevel = 3;
|
|
ep->gc.objectsInUse = 0;
|
|
ep->gc.propertiesInUse = 0;
|
|
ep->gc.collecting = 1;
|
|
|
|
resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
|
|
mark(ep);
|
|
|
|
ep->gc.collecting = 0;
|
|
ep->gc.debugLevel = oldDebugLevel;
|
|
|
|
mprLog(ep, 0, "%d objects and %d properties in use",
|
|
ep->gc.objectsInUse, ep->gc.propertiesInUse);
|
|
mprLog(ep, 0, "%d object bytes, %d property bytes and %d total",
|
|
(int) (ep->gc.objectsInUse * sizeof(EjsObj)),
|
|
(int) (ep->gc.propertiesInUse * sizeof(EjsProperty)),
|
|
(int) ((ep->gc.objectsInUse * sizeof(EjsObj) +
|
|
ep->gc.propertiesInUse * sizeof(EjsProperty))));
|
|
}
|
|
|
|
#endif
|
|
/******************************************************************************/
|
|
/*
|
|
* Return true if there is time to do a garbage collection and if we will
|
|
* benefit from it.
|
|
*/
|
|
|
|
int ejsIsTimeForGC(Ejs *ep, int timeTillNextEvent)
|
|
{
|
|
EjsGC *gc;
|
|
|
|
if (timeTillNextEvent < EJS_MIN_TIME_FOR_GC) {
|
|
/*
|
|
* Not enough time to complete a collection
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
gc = &ep->gc;
|
|
|
|
/*
|
|
* Return if we haven't done enough work to warrant a collection
|
|
* Trigger a little short of the work quota to try to run GC before
|
|
* a demand allocation requires it.
|
|
*/
|
|
if (!gc->enable || !gc->enableIdleCollect ||
|
|
(gc->workDone < (gc->workQuota - EJS_GC_MIN_WORK_QUOTA))) {
|
|
return 0;
|
|
}
|
|
|
|
#if UNUSED
|
|
mprLog(ep, 0, "Time for GC. Work done %d, time till next event %d",
|
|
gc->workDone, timeTillNextEvent);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Return the amount of memory in use by EJS
|
|
*/
|
|
|
|
uint ejsGetUsedMemory(Ejs *ep)
|
|
{
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
EjsSlab *slab;
|
|
int i, totalMemory, slabMemory;
|
|
|
|
totalMemory = 0;
|
|
for (i = 0; i < EJS_SLAB_MAX; i++) {
|
|
slab = &ep->slabs[i];
|
|
slabMemory = slab->allocCount * slab->size;
|
|
totalMemory += slabMemory;
|
|
}
|
|
return totalMemory;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Return the amount of memory allocated by EJS
|
|
*/
|
|
|
|
uint ejsGetAllocatedMemory(Ejs *ep)
|
|
{
|
|
#if BLD_FEATURE_ALLOC_STATS
|
|
EjsSlab *slab;
|
|
int i, totalMemory, slabMemory;
|
|
|
|
totalMemory = 0;
|
|
for (i = 0; i < EJS_SLAB_MAX; i++) {
|
|
slab = &ep->slabs[i];
|
|
slabMemory = (slab->allocCount + slab->freeCount) * slab->size;
|
|
totalMemory += slabMemory;
|
|
}
|
|
return totalMemory;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* On a memory allocation failure, go into graceful degrade mode. Set all
|
|
* slab allocation chunk increments to 1 so we can create an exception block
|
|
* to throw.
|
|
*/
|
|
|
|
static void ejsGracefulDegrade(Ejs *ep)
|
|
{
|
|
EjsSlab *slab;
|
|
int i;
|
|
|
|
mprLog(ep, 1, "WARNING: Memory almost depleted. In graceful degrade mode");
|
|
for (i = 0; i < EJS_SLAB_MAX; i++) {
|
|
slab = &ep->slabs[i];
|
|
slab->allocIncrement = 8;
|
|
}
|
|
ep->gc.degraded = 1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
int ejsSetGCDebugLevel(Ejs *ep, int debugLevel)
|
|
{
|
|
int old;
|
|
|
|
old = ep->gc.debugLevel;
|
|
ep->gc.debugLevel = debugLevel;
|
|
return old;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
int ejsSetGCMaxMemory(Ejs *ep, uint maxMemory)
|
|
{
|
|
int old;
|
|
|
|
old = ep->gc.maxMemory;
|
|
ep->gc.maxMemory = maxMemory;
|
|
|
|
return old;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
bool ejsBlockInUseInt(EjsVar *vp)
|
|
{
|
|
if (vp) {
|
|
#if BLD_DEBUG
|
|
if (vp->gc.magic != EJS_MAGIC) {
|
|
return 0;
|
|
}
|
|
if (vp->type == EJS_TYPE_OBJECT && vp->objectState &&
|
|
vp->objectState->gc.magic != EJS_MAGIC) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
#else
|
|
void ejsGarbageDummy() {}
|
|
|
|
#endif /* BLD_FEATURE_EJS */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
* vim:tw=78
|
|
* vim600: sw=4 ts=4 fdm=marker
|
|
* vim<600: sw=4 ts=4
|
|
*/
|