From 77e8402dd68079c0e245fc8826daf2c6ad334766 Mon Sep 17 00:00:00 2001 From: Derrell Lipman Date: Tue, 26 Sep 2006 16:58:27 +0000 Subject: r18925: Add current snapshot of the ejs-2.0 code. Tridge, will you be incorporating this? (This used to be commit 917af234a8d517f82bd42256a940608a16b988f4) --- source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c | 1214 +++++++++++++++++++++++++++ 1 file changed, 1214 insertions(+) create mode 100755 source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c (limited to 'source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c') diff --git a/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c b/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c new file mode 100755 index 0000000000..264da05721 --- /dev/null +++ b/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c @@ -0,0 +1,1214 @@ +/* + * @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 + */ -- cgit