Advertisement

Problem with instance memory management

Started by April 21, 2011 11:54 PM
8 comments, last by WitchLord 13 years, 7 months ago
What I am doing right now is working on a system that saves the state of a script object. This so I can reload the updated script and still retain all member variable values.

This system is working fine, but I am getting memory leaks with instances. Right now I am trying this on "array" class (that comes along with angelscript).

Here is a very simple test I did (it does not do any loading, etc, I just wanted to explain why I am doing this at all),

First of all:

apDestData = mpScriptEngine->CreateScriptObjectCopy(apSrcAddr, alScriptId);

which I assume creates a copy of the script object in a pointer called apDestData.

I do nothing else with this pointer other than deleting a bit little later by doing:

pScriptEngine->ReleaseScriptObject(pData, typeId);

which does not seem to release the data of the new script object. (I checked and it did not call Release() in CScriptArray).

Also when finally running the script, it seems like the newly created object is still around, and its AddRef and Release is called every time the same function is called for the orginal object. My log output:
03CC6590 calling ref 3!
0B629DD8 calling ref 3!
03CC6590 calling release 3!
0B629DD8 calling release 3![/quote]
(the number is the refcount before dec or after add. 0B629DD8 is the data I created with CreateScriptObjectCopy)

Also when calling Script engine release I get very strange output:
3CC6590 calling ref 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
03CC6590 calling release 3!
03CC6590 calling release 2!
03CC6590 calling release 1!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3!
0B629DD8 calling ref 3!
0B629DD8 calling release 3![/quote]

I cannot understand why all this happens.


So what am I doing wrong? :) Or is this perhaps some kind of bug?

EDIT:
Here is the script file (or rather the important bits from it):
class cTest {
OnStart() {
mvTest.insertLast(2);
mvTest.insertLast(3);
mvTest.insertLast(5);
mvTest.insertLast(7);
}
array<int> mvTest;

OnStart() is called once only.
There is definitely something strange going on. Can you show me just a little bit more how you're doing the copy of the object? Where do you get the original pointer from, and how are you storing the newly created pointer, etc? How are you then running the script?

The log messages you have in the addref/release methods. Did you modify the script array to add those, or are they from some other piece of code?


I'll try to reproduce the problem on my side, but I don't think I have enough information to recreate the same scenario.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement
As the code is wrapped in layers of interfaces and so on, I cannot post all the code I use, but will sum up what I think are all the important calls (pretty much any to AngelScript). If the following is not enough, I will make a special test program where I call of the angelscript stuff without my own added fluff :)

Instead of doing what I did in the previous post I decided on making it all simpler, and then work towards more complexity, hopefully making it easier to track problems.

TEST 1

First of, all the updated Release and AddRef code for array is:
void CScriptArray::AddRef() const
{
// Clear the GC flag then increase the counter
gcFlag = false;
refCount++;

hpl::Log("%p calling ref %d!\n", this, refCount);
}

void CScriptArray::Release() const
{
hpl::Log("%p calling release %d!\n", this, refCount);
// Now do the actual releasing (clearing the flag set by GC)
gcFlag = false;
if( --refCount == 0 )
{
hpl::Log(" deleting %p\n", this);
delete this;
}
}


Simply just adding log output inside the code.

The test program is this:
class cTest
{
void OnStart()
{
mvTest.insertLast(2);
}

array<int> mvTest;
}


Now what I simply do is to
0) Init angel script
1) Load the script module
2) Create a script object for cTest
3) Run OnStart once,
4) Destroy object and module
5) Release angel script.

The output is like this:
Load Script Begin
Create Class object begin
0C050648 calling ref 2!
Create script object. Object count: 3
Create Class object end
Load Script End

OnStart Begin!
OnStart End!

Release script object begin
Count: 1
Release script object end
Discard module begin
Discard module End

Release engine begin
0C050648 calling ref 3!
0C050648 calling release 3!
0C050648 calling release 2!
0C050648 calling release 1!
deleting 0C050648
Release engine done[/quote]

I create the script object with:

asIScriptObject *pObj = CreateIScriptObject(asClassName, apContext, apModule);
int lCount = pObj ->AddRef();
Log("Create script object. Object count: %d\n", lCount);


destroy the script object with:
Log("Release script object begin\n");
if(mpObject)
{
int lCount = mpObject->Release();
Log(" Count: %d\n", lCount);
}
Log("Release script object end\n");


Module is destroyed with:
Log("Discard module begin\n");
int lRet = mpScriptEngine->DiscardModule(GetName().c_str());
if(lRet < 0) Log("Failed delete of module '%s'\n", GetName().c_str());
Log("Discard module End\n");


Note that I do not mess with the "array mvTest" property in cTest at all.

In this simple example there are some things I do not understand:

- Why do the array object (0C050648) get an add ref (increasing the ref count to 2) when the cTest object is created?
- Why is not the array object (0C050648) destroyed when I destroy cTest with Release()?
- Why does array (0C050648) have a ref count of 3 when the Script Engine is released?

(another test coming in the next post)
Now for next test:

TEST 2

The setup is similar, but now I also do the back up of the data array data

So new flow of program is:
0) Init angel script
1) Load the script module
2) Create a script object for cTest
3) Create a copy of "array<int> mvTest" and delete it after having done so.
4) Run OnStart once,
5) Destroy object and module
6) Release angel script.

The copy process looks like this (because it is simplified, it looks a bit silly, but I hope it still works :) ):
for(int i=0; i< mpObject->GetPropertyCount(); ++i)
{
tString sPropName = mpObject->GetPropertyName(i);
void *pPropAddr = mpObject->GetAddressOfProperty(i);
int lPropTypeId = mpObject->GetPropertyTypeId(i);

Log("Save array start!\n");
void* pData = mpScriptEngine->CreateScriptObjectCopy(pPropAddr , lPropTypeId);
Log("new data: %p!\n", pData );
Log("Save array end!\n");

Log("Release instance of '%s' reference: %p begin\n", sPropName .c_str(), pData );
pScriptEngine->ReleaseScriptObject(pData, lPropTypeId );
Log("Release end\n");
}


The output now becomes:
Create Class object begin
0BF40648 calling ref 2!
Create script object. Object count: 3
Create Class object end
Load Script End
Save array start!
0BF406C0 calling ref 2!
new data: 0BF406C0!
Save array end!
Release instance of 'array' reference: 0BF406C0 begin
Release end

On Start Begin!
On Start End!

Release script object begin
Count: 1
Release script object end
Discard module begin
Discard module End

Release engine begin
0BF40648 calling ref 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF40648 calling release 3!
0BF40648 calling release 2!
0BF40648 calling release 1!
deleting 0BF40648
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
0BF406C0 calling ref 3!
0BF406C0 calling release 3!
Release engine done[/quote]

As can be seen, 0BF406C0 is never deleted, even though it seems like it could be done so at step 3.
<br>In this simple example there are some things I do not understand:<br><br>- Why do the array object (0C050648) get an add ref (increasing the ref count to 2) when the cTest object is created?<br>- Why is not the array object (0C050648) destroyed when I destroy cTest with Release()?<br>- Why does array (0C050648) have a ref count of 3 when the Script Engine is released?<br>
<br><br><br>The array object has logic to be garbage collected, because it can potentially create circular references. When the array object is instanciated it notifies the GC, which where the add ref is coming from. <br><br>As the array object is garbage collected, it will only be destroyed when the garbage collector runs, either when you call the engine-&gt;GarbageCollect method, or when the engine is released.<br><br>The ref count reaches 3 because, while the GC is working on the object to determine if it can be destroyed, it increases the ref count temporarily so the object isn't destroyed too early.<br><br><br><br><br>From your second test I see a problem with the engine-&gt;ReleaseScriptObject(). You should have seen a log entry that the release method of the array is called, but that didn't happen. For this reason, the ref count never reaches 0 so the array object can be destroyed. <br><br>I need to investigate further exactly why the release method wasn't called, but it looks like a bug in AngelScript and not in your code.<br><br><br>Thanks,<br><br>Andreas<br><br><br><br><br><br>

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Ah, makes sense that the Garbage Collection handles it!

Right now I can garbage collection at every logic update with the asGC_ONE_STEP flag. This is the way to do it right?
Advertisement
http://www.angelcode.com/angelscript/sdk/docs/manual/doc_gc.html


You can call the garbage collector with asGC_FULL_CYCLE | asDESTROY_GARBAGE often, at least once per frame. A call with these flags just do a quick sweep over the known objects to see if any of them are ready to be destroyed.

You'll also want to make a call (or a few of them) with asGC_ONE_STEP | asDETECT_GARBAGE. This is the call that will eventually detect and allow the destruction of objects kept alive through circular references.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

As I was trying to reproduce this problem I realized that you're having more problems than just the memory issue. It seems the GetPropertyName() is also returning the wrong name, you should get the name of the property, i.e. 'mvTest', not the type of the property.

Neither of these problems happens for me, which makes me think that you may be having a problem with a desynchronized compilation of the library and the linkage. I've seen this happen with some compilers before. Sometimes it doesn't recompile everything that needed to be recompiled and it linked the modules incorrectly without returning any error.

I suggest you do a full rebuild of the library and then try again.

Here's the test I wrote to try to reproduce the problem:


engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
engine->SetMessageCallback(asMETHOD(COutStream, Callback), &out, asCALL_THISCALL);
RegisterScriptArray(engine, false);
asIScriptModule *mod = engine->GetModule("", asGM_ALWAYS_CREATE);
mod->AddScriptSection("script",
"class cTest \n"
"{ \n"
" void OnStart() \n"
" { \n"
" mvTest.insertLast(2); \n"
" } \n"
" array<int> mvTest; \n"
"} \n");
r = mod->Build();
if( r < 0 )
TEST_FAILED;

asIScriptObject *obj = (asIScriptObject *)engine->CreateScriptObject(mod->GetTypeIdByDecl("cTest"));
int lCount = obj->AddRef(); // This isn't necessary
printf("Create script object. Object count: %d\n", lCount);

std::string sPropName = obj->GetPropertyName(0);
void *pPropAddr = obj->GetAddressOfProperty(0);
int lPropTypeId = obj->GetPropertyTypeId(0);
printf("Save array start!\n");
void *pData = engine->CreateScriptObjectCopy(pPropAddr , lPropTypeId);
printf("new data: %p!\n", pData );
printf("Save array end!\n");

printf("Release instance of '%s' reference: %p begin\n", sPropName .c_str(), pData );
engine->ReleaseScriptObject(pData, lPropTypeId);
printf("Release end\n");

int func = obj->GetObjectType()->GetMethodIdByName("OnStart");
asIScriptContext *ctx = engine->CreateContext();
ctx->Prepare(func);
ctx->SetObject(obj);
r = ctx->Execute();
if( r != asEXECUTION_FINISHED )
{
TEST_FAILED;
if( r == asEXECUTION_EXCEPTION )
PrintException(ctx);
}
ctx->Release();

printf("Release script object begin\n");
lCount = obj->Release();
obj->Release();
printf(" Count: %d\n", lCount);
printf("Release script object end\n");

printf("Discard module begin\n");
r = engine->DiscardModule("");
if(r < 0) printf("Failed delete of module\n");
printf("Discard module End\n");

engine->Release();

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Afraid it was all my fault :(

The problem was simply that the type id from array is different depending on what type it contains. What I had done was to just save the type id upon creation (at RegisterObjectType) and then used that when calling ReleaseScriptObject.

Once I fixed this, all the memory issues were over! :)

Thanks for all the support, and really sorry for leading you on a red herring hunt :/
That's ok, I'm just glad you found the problem.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement