Yocto-Visualization, Linux, and memory leaks

Yocto-Visualization, Linux, and memory leaks

A customer recently complained about an massive memory leak in the Linux version of Yocto-Visualization. We were first rather surprised because, at Yoctopuce, we monitor several experiments 24/7 through instances of Yocto-Visualization installed on Raspberry Pi. But we checked and, indeed, there is an severe issue with the current version of Mono 6.



More specifically, the issue is located in the libgdiplus library which is installed with Mono 6: it contains an nasty bug which is the source of this memory leak. The way to solve this issue is sophisticated enough for us to make a post out of it.

Highlighting the issue

It didn't take us long to determine that the memory leak occurred in the "renderer" part of Yocto-Visualization. More exactly, the issue appears in the double-buffering code. To highlight it, we wrote a short C# application which creates an animation in a "picturebox" element. Each frame is drawn by a function called 25 times per second thanks to a timer. Here is the code of the function:

 private void timer1_Tick(object sender, EventArgs e)
    {
      int w = pictureBox1.Size.Width;
      int h = pictureBox1.Size.Height;
      int centerX = w >> 1;
      int centerY = h >> 1;
      Bitmap DrawArea = new Bitmap(w, h);
      pictureBox1.Image = DrawArea;
      Graphics g = Graphics.FromImage(DrawArea);
      g.FillRectangle(whiteBrush, new Rectangle(0, 0, w, h));
      double radius = Math.Min(0.9 * w / 2, 0.9 * h / 2);
      for (int i=0;i<5;i++)
       { double angle1 = Math.PI * (i * 72 + ofset) / 180;
         double angle2 = Math.PI * ((i+2) * 72 + ofset) / 180;
         double x1 = centerX + radius * Math.Cos(angle1);
         double y1 = centerY + radius * Math.Sin(angle1);
         double x2 = centerX + radius * Math.Cos(angle2);
         double y2 = centerY + radius * Math.Sin(angle2);
         g.DrawLine(blackPen,(float) x1, (float)y1, (float)x2, (float)y2);
       }
      g.Dispose();
      ofset++;
    }


Here is the result:

A very simple animation
A very simple animation


If you run this application under Windows, it runs indefinitely. Memory use does indeed show a sawtooth pattern, but it works.

Memory usage under Windows (Process Explorer)
Memory usage under Windows (Process Explorer)


However, it you run it with Mono 6 under Linux, it uses up all the memory and ends up crashing after a few minutes. The larger the window, the faster the crash.

Memory usage under Linux (System Monitor)
Memory usage under Linux (System Monitor)


The problem comes specifically from the line

 pictureBox1.Image = DrawArea;


This line is used to assign the new bitmap buffer when computing the animation. Since the old buffer isn't referenced anymore, the garbage collector is supposed to notice this after a while and to free the corresponding memory. Except that with Mono 6, it doesn't work. We had the idea to add a piece of code to explicitly tell the garbage collector that we didn't need the old buffer anymore:

 Image previous = pictureBox1.Image;
 pictureBox1.Image = DrawArea;
 if (previous!=null)  previous.Dispose();


Rather logically, this enabled us to smooth and lower memory consumption under Windows.

Memory consumption under Windows is smoothed
Memory consumption under Windows is smoothed


Unfortunately, the code modification had absolutely no effect in the Linux version which still crashes pitifully after a few tens of seconds.

In fact, this memory leak was more or less fixed last April in version 6.0.5 of libgdiplus. Unfortunately, at the time of writing, this corrected version is available only in source code. So you need to download it and compile it yourself.

Fixing the problem

Checking the current version

If your Linux installation of Yocto-Visualization crashes after a few minutes, the first thing to do is to check that you have the incriminated version of Mono. Do so with the mono -v command (remember that the command to install Mono is "sudo apt install mono-complete"). In the screen capture below, you see that we have version 6.

We have Mono version 6
We have Mono version 6


Then you must check the libgdiplus version which was installed at the same time as "mono-complete" in "/usr/lib". Normally, the version number is explicitly given in the file name, but here the file is unfortunately named libgdiplus.so.0.0.0. We can however use a non-conventional method to find the file version by looking in the file for the "6.0." character string with the command "strings /ust/lib/lingdiplus.so | grep "6\.0\.". In the screen capteur below, we see that we have version "6.0.4" while we need version 6.0.5 at least.

We have version 6.0.4
We have version 6.0.4


Downloading the sources of libgdiplus

You can find the sources of libgdiplus on the Mono site. Don't use the version available on GitHub, it is incomplete: if you try to compile it, you are going to get the error message "Error: Couldn't find the required submodules. This usually happens when using an archive from GitHub".

So download the libgdiplus-6.0.5.tar.gz archive, de-tar it wherever you want, you should have a libgdiplus-6.0.5 directory.

Before compiling, you also need to install the dependencies of this library. To do so, enter the following command line:

sudo apt-get install libgif-dev autoconf libtool automake build-essential gettext libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev


Compiling the libgdiplus sources

Go to the libgdiplus-6.0.5 directory and enter the following command:

./configure


then type:

make


Normally, your library should be ready.

Installation

To install your newly compiled library, you should enter

sudo make install


Your library is now installed, but in the /usr/local/lib directory while Mono will look for it in /usr/lib thanks to two symbolic links. You must therefore erase these two links and replace them with new ones pointing to the new version:

cd /usr/lib sudo rm libgdiplus.so sudo rm libgdiplus.so.0 sudo ln -s /usr/local/lib/libgdiplus.so.0.0.0 libgdiplus.so sudo ln -s /usr/local/lib/libgdiplus.so.0.0.0 libgdiplus.so.0


Finally, you can check that it works by retyping the command:

strings /usr/lib/libgdiplus.so | grep "6\.0\."


which this time should return "?6.0.5"

Test

Now, if we start again our test animation, it doesn't monopolize all the memory, but only if we launch the version which explicitly tells the garbage collector that it must free the memory of unused bitmaps. Apparently, it's still unable to realize this on its own.

Tadaaa! no more memory leak!
Tadaaa! no more memory leak!


Conclusion

If you encounter this memory leak issue under Linux, you must do two things to solve the problem:

  1. Install the libgdiplus 6.0.5 library, even if you have to compile it yourself
  2. Install version 43172 of Yocto-Visualization, which contains the code to ask the garbage collector to stop procrastinating.

Under Windows, installing the latest version of Yocto-Visualization is also a plus: memory consumption is greatly lowered thanks to the smoothing of the sawtooth behavior.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.