void asCGarbageCollector::ReportUndestroyedObjects() { for( asUINT n = 0; n < gcOldObjects.GetLength(); n++ ) { asSObjTypePair gcObj = GetOldObjectAtIdx(n); asCString msg; msg.Format(TXT_GC_CANNOT_FREE_OBJ_OF_TYPE_s, gcObj.type->name.AddressOf()); engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf()); } }
int asCGarbageCollector::ReportAndReleaseUndestroyedObjects() { // This function will only be called as the engine is shutting down int items = 0; for( asUINT n = 0; n < gcOldObjects.GetLength(); n++ ) { asSObjTypePair gcObj = GetOldObjectAtIdx(n); int refCount = 0; if( gcObj.type->beh.gcGetRefCount && engine->scriptFunctions[gcObj.type->beh.gcGetRefCount] ) refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount); // Report the object as not being properly destroyed asCString msg; msg.Format(TXT_d_GC_CANNOT_FREE_OBJ_OF_TYPE_s_REF_COUNT_d, gcObj.seqNbr, gcObj.type->name.AddressOf(), refCount - 1); engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf()); // Add additional info for builtin types if( gcObj.type->name == "_builtin_function_" ) { // Unfortunately we can't show the function declaration here, because the engine may have released the parameter list already so the declaration would only be misleading // We need to show the function type too as for example delegates do not have a name msg.Format(TXT_PREV_FUNC_IS_NAMED_s_TYPE_IS_d, reinterpret_cast<asCScriptFunction*>(gcObj.obj)->GetName(), reinterpret_cast<asCScriptFunction*>(gcObj.obj)->GetFuncType()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } else if( gcObj.type->name == "_builtin_objecttype_" ) { msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCObjectType*>(gcObj.obj)->GetName()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } else if( gcObj.type->name == "_builtin_globalprop_" ) { msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCGlobalProperty*>(gcObj.obj)->name.AddressOf()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } // Release the reference that the GC holds if the release functions is still available if( gcObj.type->beh.release && engine->scriptFunctions[gcObj.type->beh.release] ) engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release); items++; } return items; }
int asCGarbageCollector::ReportAndReleaseUndestroyedObjects() { int items = 0; for( asUINT n = 0; n < gcOldObjects.GetLength(); n++ ) { asSObjTypePair gcObj = GetOldObjectAtIdx(n); // Report the object as not being properly destroyed asCString msg; msg.Format(TXT_GC_CANNOT_FREE_OBJ_OF_TYPE_s, gcObj.type->name.AddressOf()); engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf()); // Release the reference that the GC holds if the release functions is still available if( gcObj.type->beh.release && engine->scriptFunctions[gcObj.type->beh.release] ) engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release); items++; } return items; }
int asCGarbageCollector::ReportAndReleaseUndestroyedObjects() { int items = 0; for( asUINT n = 0; n < gcOldObjects.GetLength(); n++ ) { asSObjTypePair gcObj = GetOldObjectAtIdx(n); int refCount = 0; if( gcObj.type->beh.gcGetRefCount && engine->scriptFunctions[gcObj.type->beh.gcGetRefCount] ) refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount); // Report the object as not being properly destroyed asCString msg; msg.Format(TXT_GC_CANNOT_FREE_OBJ_OF_TYPE_s_REF_COUNT_d, gcObj.type->name.AddressOf(), refCount - 1); engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf()); // Add additional info for builtin types if( gcObj.type->name == "_builtin_function_" ) { msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCScriptFunction*>(gcObj.obj)->GetName()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } else if( gcObj.type->name == "_builtin_objecttype_" ) { msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCObjectType*>(gcObj.obj)->GetName()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } else if( gcObj.type->name == "_builtin_globalprop_" ) { msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCGlobalProperty*>(gcObj.obj)->name.AddressOf()); engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf()); } // Release the reference that the GC holds if the release functions is still available if( gcObj.type->beh.release && engine->scriptFunctions[gcObj.type->beh.release] ) engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release); items++; } return items; }
int asCGarbageCollector::IdentifyGarbageWithCyclicRefs() { for(;;) { switch( detectState ) { case clearCounters_init: detectState = clearCounters_loop; break; case clearCounters_loop: { // Decrease reference counter for all objects removed from the map asSMapNode<void*, asSIntTypePair> *cursor = 0; gcMap.MoveFirst(&cursor); if( cursor ) { void *obj = gcMap.GetKey(cursor); asSIntTypePair it = gcMap.GetValue(cursor); engine->CallObjectMethod(obj, it.type->beh.release); ReturnNode(gcMap.Remove(cursor)); return 1; } detectState = buildMap_init; } break; case buildMap_init: detectIdx = 0; detectState = buildMap_loop; break; case buildMap_loop: { // Build a map of objects that will be checked, the map will // hold the object pointer as key, and the gcCount and the // object's type as value. As objects are added to the map the // gcFlag must be set in the objects, so we can be verify if // the object is accessed during the GC cycle. // If an object is removed from the gcObjects list during the // iteration of this step, it is possible that an object won't // be used during the analyzing for cyclic references. This // isn't a problem, as the next time the GC cycle starts the // object will be verified. if( detectIdx < gcOldObjects.GetLength() ) { // Add the gc count for this object asSObjTypePair gcObj = GetOldObjectAtIdx(detectIdx); int refCount = 0; if( gcObj.type->beh.gcGetRefCount ) refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount); if( refCount > 1 ) { asSIntTypePair it = {refCount-1, gcObj.type}; gcMap.Insert(GetNode(gcObj.obj, it)); // Increment the object's reference counter when putting it in the map engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref); // Mark the object so that we can // see if it has changed since read engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.gcSetFlag); } detectIdx++; // Let the application work a little return 1; } else detectState = countReferences_init; } break; case countReferences_init: { gcMap.MoveFirst(&gcMapCursor); detectState = countReferences_loop; } break; case countReferences_loop: { // Call EnumReferences on all objects in the map to count the number // of references reachable from between objects in the map. If all // references for an object in the map is reachable from other objects // in the map, then we know that no outside references are held for // this object, thus it is a potential dead object in a circular reference. // If the gcFlag is cleared for an object we consider the object alive // and referenced from outside the GC, thus we don't enumerate its references. // Any new objects created after this step in the GC cycle won't be // in the map, and is thus automatically considered alive. if( gcMapCursor ) { void *obj = gcMap.GetKey(gcMapCursor); asCObjectType *type = gcMap.GetValue(gcMapCursor).type; gcMap.MoveNext(&gcMapCursor, gcMapCursor); if( engine->CallObjectMethodRetBool(obj, type->beh.gcGetFlag) ) { engine->CallObjectMethod(obj, engine, type->beh.gcEnumReferences); } // Allow the application to work a little return 1; } else detectState = detectGarbage_init; } break; case detectGarbage_init: { gcMap.MoveFirst(&gcMapCursor); liveObjects.SetLength(0); detectState = detectGarbage_loop1; } break; case detectGarbage_loop1: { // All objects that are known not to be dead must be removed from the map, // along with all objects they reference. What remains in the map after // this pass is sure to be dead objects in circular references. // An object is considered alive if its gcFlag is cleared, or all the // references were not found in the map. // Add all alive objects from the map to the liveObjects array if( gcMapCursor ) { asSMapNode<void*, asSIntTypePair> *cursor = gcMapCursor; gcMap.MoveNext(&gcMapCursor, gcMapCursor); void *obj = gcMap.GetKey(cursor); asSIntTypePair it = gcMap.GetValue(cursor); bool gcFlag = engine->CallObjectMethodRetBool(obj, it.type->beh.gcGetFlag); if( !gcFlag || it.i > 0 ) { liveObjects.PushLast(obj); } // Allow the application to work a little return 1; } else detectState = detectGarbage_loop2; } break; case detectGarbage_loop2: { // In this step we are actually removing the alive objects from the map. // As the object is removed, all the objects it references are added to the // liveObjects list, by calling EnumReferences. Only objects still in the map // will be added to the liveObjects list. if( liveObjects.GetLength() ) { void *gcObj = liveObjects.PopLast(); asCObjectType *type = 0; // Remove the object from the map to mark it as alive asSMapNode<void*, asSIntTypePair> *cursor = 0; if( gcMap.MoveTo(&cursor, gcObj) ) { type = gcMap.GetValue(cursor).type; ReturnNode(gcMap.Remove(cursor)); // We need to decrease the reference count again as we remove the object from the map engine->CallObjectMethod(gcObj, type->beh.release); // Enumerate all the object's references so that they too can be marked as alive engine->CallObjectMethod(gcObj, engine, type->beh.gcEnumReferences); } // Allow the application to work a little return 1; } else detectState = verifyUnmarked_init; } break; case verifyUnmarked_init: gcMap.MoveFirst(&gcMapCursor); detectState = verifyUnmarked_loop; break; case verifyUnmarked_loop: { // In this step we must make sure that none of the objects still in the map // has been touched by the application. If they have then we must run the // detectGarbage loop once more. if( gcMapCursor ) { void *gcObj = gcMap.GetKey(gcMapCursor); asCObjectType *type = gcMap.GetValue(gcMapCursor).type; bool gcFlag = engine->CallObjectMethodRetBool(gcObj, type->beh.gcGetFlag); if( !gcFlag ) { // The unmarked object was touched, rerun the detectGarbage loop detectState = detectGarbage_init; } else gcMap.MoveNext(&gcMapCursor, gcMapCursor); // Allow the application to work a little return 1; } else { // No unmarked object was touched, we can now be sure // that objects that have gcCount == 0 really is garbage detectState = breakCircles_init; } } break; case breakCircles_init: { gcMap.MoveFirst(&gcMapCursor); detectState = breakCircles_loop; } break; case breakCircles_loop: case breakCircles_haveGarbage: { // All objects in the map are now known to be dead objects // kept alive through circular references. To be able to free // these objects we need to force the breaking of the circle // by having the objects release their references. if( gcMapCursor ) { numDetected++; void *gcObj = gcMap.GetKey(gcMapCursor); asCObjectType *type = gcMap.GetValue(gcMapCursor).type; engine->CallObjectMethod(gcObj, engine, type->beh.gcReleaseAllReferences); gcMap.MoveNext(&gcMapCursor, gcMapCursor); detectState = breakCircles_haveGarbage; // Allow the application to work a little return 1; } else { // If no garbage was detected we can finish now if( detectState != breakCircles_haveGarbage ) { // Restart the GC detectState = clearCounters_init; return 0; } else { // Restart the GC detectState = clearCounters_init; return 1; } } } } // switch } // Shouldn't reach this point UNREACHABLE_RETURN; }
int asCGarbageCollector::DestroyOldGarbage() { for(;;) { switch( destroyOldState ) { case destroyGarbage_init: { // If there are no objects to be freed then don't start if( gcOldObjects.GetLength() == 0 ) return 0; destroyOldIdx = (asUINT)-1; destroyOldState = destroyGarbage_loop; } break; case destroyGarbage_loop: case destroyGarbage_haveMore: { // If the refCount has reached 1, then only the GC still holds a // reference to the object, thus we don't need to worry about the // application touching the objects during collection. // Destroy all objects that have refCount == 1. If any objects are // destroyed, go over the list again, because it may have made more // objects reach refCount == 1. if( ++destroyOldIdx < gcOldObjects.GetLength() ) { asSObjTypePair gcObj = GetOldObjectAtIdx(destroyOldIdx); if( gcObj.type->beh.gcGetRefCount == 0 ) { // If circular references are formed with registered types that hasn't // registered the GC behaviours, then the engine may be forced to free // the object type before the actual object instance. In this case we // will be forced to skip the destruction of the objects, so as not to // crash the application. asCString msg; msg.Format(TXT_GC_CANNOT_FREE_OBJ_OF_TYPE_s, gcObj.type->name.AddressOf()); engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf()); // Just remove the object, as we will not bother to destroy it numDestroyed++; RemoveOldObjectAtIdx(destroyOldIdx); destroyOldIdx--; } else if( engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount) == 1 ) { // Release the object immediately // Make sure the refCount is really 0, because the // destructor may have increased the refCount again. bool addRef = false; if( gcObj.type->flags & asOBJ_SCRIPT_OBJECT ) { // Script objects may actually be resurrected in the destructor int refCount = ((asCScriptObject*)gcObj.obj)->Release(); if( refCount > 0 ) addRef = true; } else engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release); // Was the object really destroyed? if( !addRef ) { numDestroyed++; RemoveOldObjectAtIdx(destroyOldIdx); destroyOldIdx--; } else { // Since the object was resurrected in the // destructor, we must add our reference again engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref); } destroyOldState = destroyGarbage_haveMore; } // Allow the application to work a little return 1; } else { if( destroyOldState == destroyGarbage_haveMore ) { // Restart the cycle destroyOldState = destroyGarbage_init; } else { // Restart the cycle destroyOldState = destroyGarbage_init; // Return 0 to tell the application that there // is no more garbage to destroy at the moment return 0; } } } break; } } // Shouldn't reach this point UNREACHABLE_RETURN; }