Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: SFML.Text memory leak (?)  (Read 8369 times)

0 Members and 1 Guest are viewing this topic.

CBenni::O

  • Newbie
  • *
  • Posts: 48
    • View Profile
SFML.Text memory leak (?)
« on: June 06, 2013, 12:17:02 am »
I called the following code segment once per frame:

Text txt = new Text("sometext", font);
txt.Position = new Vector2f(0, 30);
window.Draw(txt);

And I received a massive memory leak (~600kb/sec at 60 fps) that didnt get cleaned up by the garbage collector. Commenting the code out or moving the creation of txt outside of the loop resolved the memory usage increase. I think there might be some problem with the constructor (as the memory obviously doesnt get cleaned up afterwards). You might want to look into this?

bye, CBenni
« Last Edit: June 08, 2013, 07:12:08 pm by CBenni::O »
42!
Metal will never die!

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: SFML.Text memory leak (?)
« Reply #1 on: June 06, 2013, 05:08:56 pm »
I will do some more tests at home when I get off work, but it does look as if the .NET binding do have a memory leak from the quick glance I took at the source code.
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: SFML.Text memory leak (?)
« Reply #2 on: June 07, 2013, 02:16:12 am »
Ok after some tests there seems to be something strange with some of the deconstructors and the OpenGL contexts. Lets start with some sample code.

This first example causes a memory leak. I also included the Textures decontructor to show which descontructor is being called.

static void Main(string[] args)
        {
            for (int c = 0; c < 10000; c++)
            {
                SFML.Graphics.Texture text = new SFML.Graphics.Texture(@"...file path...");
            }
        }

        ~ObjectBase()
        {
            Dispose(false);
        }
 


Now here is an example that does not have a memory leak. And the decontructor for reference.

static void Main(string[] args)
        {
            for (int c = 0; c < 10000; c++)
            {
                SFML.Graphics.Texture text = new SFML.Graphics.Texture(@"C:\Users\Zachariah Brown\Pictures\96134.jpg");
                text.Dispose();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 

This code has the same memory leak regardless if you use a Texture, Font, or RenderTexture. The difference between the two decontructors is the Dispose(bool) call. Now when we break into that Dispose call we will see the following.

private void Dispose(bool disposing)
        {
            if (myCPointer != IntPtr.Zero)
            {
                Destroy(disposing);
                myCPointer = IntPtr.Zero;
            }
        }
        protected abstract void Destroy(bool disposing);
 

The memory leak happens in the implementation of the Destroy(bool) call. Lets look at the Textures's Destroy implementation.

protected override void Destroy(bool disposing)
            {
                if (!myExternal)
                {
                    if (!disposing)
                        Context.Global.SetActive(true);

                    sfTexture_destroy(CPointer);

                    if (!disposing)
                        Context.Global.SetActive(false);
                }
            }
 

Look at that Context.Global.SetActive(bool) call. Now if look back to the example that does not cause the memory link you will notice Destroy will be called with this.

Destroy(true);
 

Since Destroy is called with true that means we make 0 calls to the OpenGL context. The memory leak comes from calling SetActive(bool) on the OpenGL context. More specifically when Context.Global.SetActive(false); is called. As soon as you comment out the SetActive(bool) call the first example that originally leaked memory no longer leaks.

Now this brings out a few questions (aimed at Laurent).

  • Why is SetActive(false); leaking memory? I took from this thread that the OpenGL contexts are only supposed to leak memory when multiple threads come into play.
  • Why are the deconstructors calling Dispose(bool) differently? Shouldn't the unmanaged objects be disposed in the same way regardless if they go out of scope or have the decontructor explicitly called?

On a side note Laurent, I also took a look at the way the .NET bindings handles contexts and all of the contexts use a single global context.

public static Context Global
            {
                get
                {
                    if (ourGlobalContext == null)
                        ourGlobalContext = new Context();

                    return ourGlobalContext;
                }
            }
private static Context ourGlobalContext = null;
 

So if the bindings only ever use 1 context, why is there even a need to call SetActive(bool) on the global context?
« Last Edit: June 07, 2013, 02:21:25 am by zsbzsb »
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

Kalishir

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: SFML.Text memory leak (?)
« Reply #3 on: June 07, 2013, 04:49:49 am »
I'm pretty sure this problem exists with the Texture deconstructor too, which would make sense being they both use the ObjectBase deconstructor.

I had the same problem when not calling texture.Dispose(), but only using Windows XP. The error did not occur on my Win8 machine. Both machines are using VS2010Express.

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: SFML.Text memory leak (?)
« Reply #4 on: June 07, 2013, 03:58:22 pm »
The decontructor I showed above was the Textures decontructor. And yes the Texture, Font, RenderTexture all have that strange setup in their decontructors.
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

Kalishir

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: SFML.Text memory leak (?)
« Reply #5 on: June 07, 2013, 06:46:07 pm »
So you did. I should learn to read. I assumed that you had used the font deconstructor as he said his issue was with Text objects not being cleaned up.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: SFML.Text memory leak (?)
« Reply #6 on: June 07, 2013, 07:11:12 pm »
Isn't non-deterministic destruction a fundamental problem of languages with garbage collectors? I think that's why C# offers the dispose pattern or the RAII-like using statement.

By the way, it's destructor (not deconstructor) ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: SFML.Text memory leak (?)
« Reply #7 on: June 07, 2013, 08:29:35 pm »
Isn't non-deterministic destruction a fundamental problem of languages with garbage collectors? I think that's why C# offers the dispose pattern or the RAII-like using statement.

When objectX implements IDisposable then calling objectXinstance.Dispose() is the same as using (objectX objectXinstance = new objectX()) { }.

In .NET it is always best to call Dispose on an object if it implements IDisposable. However Dispose is not always called so it is best practice in the destructor that is called when the object goes out of scope (~objectX() { }) to call Dispose.

The problem here is that when the SFML .NET bindings call Dispose from the destructor they are calling it with a different boolean value than if the user explicitly called Dispose or used the using statement. This leads to the bindings calling SetActive(true) and then SetActive(false) which for some reason is causing a memory leak.

The solution? Remove the calls to SetActive(bool) or make the destructor call Dispose with the same boolean value as if the user explicitly called Dispose.

Quote
By the way, it's destructor (not deconstructor) ;)

Well Firefox spell check doesn't like it spelled either way :D
« Last Edit: June 07, 2013, 08:31:27 pm by zsbzsb »
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: SFML.Text memory leak (?)
« Reply #8 on: June 07, 2013, 09:04:35 pm »
Quote
The problem here is that when the SFML .NET bindings call Dispose from the destructor they are calling it with a different boolean value than if the user explicitly called Dispose or used the using statement.
This is the standard way of doing it, look at the MSDN example.

I don't remember why it is done this way, but when I implemented it (many years ago!) it was very clear ;)

Quote
This leads to the bindings calling SetActive(true) and then SetActive(false) which for some reason is causing a memory leak.
Sorry I didn't read the full discussion; are you sure of that? How did you check? And where is the leak exactly? OpenGL drivers are known to cause leaks (either true or false positives, it depends).

Quote
The solution? Remove the calls to SetActive(bool) or make the destructor call Dispose with the same boolean value as if the user explicitly called Dispose.
Trust me, if it's done this way, it's not just to create a memory leak :P
Laurent Gomila - SFML developer

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: SFML.Text memory leak (?)
« Reply #9 on: June 07, 2013, 09:36:55 pm »
Quote
This is the standard way of doing it, look at the MSDN example.

I don't remember why it is done this way, but when I implemented it (many years ago!) it was very clear

Ok don't change the boolean value in the call, but I think you misunderstand the use of that boolean. Take a look at the following quote taken from here.

Quote
   
  • disposing == true: the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed.
  • disposing == false: the method has been called by the runtime from inside the finalizer, and you should not reference other objects. Only unmanaged resources can be disposed.

The value is simply to determine if you should also dispose of managed objects. Unmanaged objects (raw pointers [Intptr]) are always do be disposed of in the same way. What the SFML .NET bindings do is read that disposing value and then if it is false call SetActive(true) and SetActive(false). The disposing value will be false if the managed object's default constructor is called.

        ~ObjectBase()
        {
            Dispose(false);
        }

The question is Laurent, why are you making calls to the OpenGL context when the garbage collector (same as when the object goes out of scope) cleans up instead of the user (through an explicit Dispose call)? The OpenGL calls come from when you check the value of that disposing boolean and then if it is false you call SetActive(true) and SetActive(false).


Quote
Sorry I didn't read the full discussion; are you sure of that? How did you check? And where is the leak exactly? OpenGL drivers are known to cause leaks (either true or false positives, it depends).

In the example where the leak happens, I let the Texture go out of scope. This will then cause the default destructor to be called with Dispose(false). Then in the Destroy function if Dispose was called with a false value you call SetActive(false) and SetActive(true). See the Destroy function below. Now if I comment out the call to SetActive their is no longer a memory leak. I see the memory leak in task manager (I know...) the memory usage continues to go through the roof and eventually SFML crashes because it can't load the texture.

But lets say its my drivers that are leaking when SetActive is called - my point above still stands, why make the destructor different for unmanaged memory depending on where the memory cleanup is coming from?

protected override void Destroy(bool disposing)
            {
                if (!myExternal)
                {
                    if (!disposing)
                        Context.Global.SetActive(true);

                    sfTexture_destroy(CPointer);

                    if (!disposing)
                        Context.Global.SetActive(false);
                }
            }

Either way we should make the destructor make the same calls to the OpenGL context.


Quote
Trust me, if it's done this way, it's not just to create a memory leak :P

Not to bloat this thread anymore, but I would love to learn more about OpenGL contexts. Especially what effect SetActive(bool) has on freeing resources.
« Last Edit: June 07, 2013, 09:42:30 pm by zsbzsb »
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: SFML.Text memory leak (?)
« Reply #10 on: June 07, 2013, 09:50:39 pm »
I'm sorry if I'm unprecise in my answers, but this was done a very long time ago and I never had to touch the code since then.

That being said, I remember a problem when objects were disposed by the GC: sometimes it could happen after the main thread was terminated, in the GC thread I guess. As long as you are in the main thread, SFML (C++) ensures that there is always an active OpenGL context, so no problem. But if SFML resources are destroyed after the main thread is gone, I need to activate a context to be able to destroy them. If I remember correctly, this is the reason of this hackish code.
Laurent Gomila - SFML developer