Big memory leak when using RGBSurface

Heads up - there’s a huge memory leak in the current Xojo version when using RGBSurface:

https://tracker.xojo.com/xojoinc/xojo/-/issues/75794

This method will leak the RGBSurface’s data without leaving any objects behind in the stack:

Public Sub LeakMemory(p as picture)
  dim r as RGBSurface
  
  r=p.RGBSurface
  
  return
End Sub

Make a loop, check if memory consumption grows indefinitely or at some point starts to shrink. It can be a leak or the MM may be deferring some disposals (maybe starting in Sonoma).

Check if the behavior is consistent in previous OS versions.

Well, calling RGBSurface creates a new RGBSurface object each time you query.
But this object gets destroyed as far as I see here.

Do you have a sample project to show this problem?

The sample project attached to the issue demonstrates the issue using a loop. You can use Activity Monitor to observe the increased RAM usage, and the project itself tracks Runtime.ObjectCount.

I discovered this in a larger project, where I noticed a very significant slowdown after a few thousand calls to a similar function. The memory usage in that scenario never decreased.

If I remove the Breaks in your code, the memory always goes back to 15MB here, macOS 13.6.4 Intel, Xojo2023r4.

Good to know. The code leaks memory with or without the breaks on my machine.

Make sure Xojo staff tests with your macOS version (Sonoma). Different versions of Sonoma may produce different results.

Could this be due to having a picture, which on demand gets loaded and decompressed?
Then stays in memory for later freeing.

You know, objective-c memory management usually collects objects to release and does that when the app has a free cycle in the event loop.
So memory gets released when app comes back to the main loop.

Interesting. If I remove all the breaks in the code, the memory usage eventually returns to nominal. Perhaps Christian’s explanation carries some weight here.

Nonetheless, this sort of delayed cleanup definitely acts in a way that is contrary to our expectations of how Xojo operates: that once an object is destroyed, it no longer occupies any space in memory. This is a very important principle, and if it is no longer the case, needs to be clearly documented and any workarounds suggested as well. For example, should I call YieldToNextThread to allow the memory to be released?

Double bingo.

Do some experiments.

The reason the memory never decreased in my larger app is that the full process it is doing requires several hours of dedicated computer time to complete - so it never reached a point in the code where this mysteriously delayed cleanup would occur, because the app got so slow due to memory bloat that I always ended up killing it.

The code isn’t in a thread, so that sort of Yielding isn’t an option.

So you should not ask if that option should be used as you knew beforehand it wasn’t an option. :rofl:

And instead of going for the quick jab, you should examine your assumptions. In this case, you presume I knew it wasn’t an option beforehand. This is not the case.

This is supposed to be a helpful forum. The other replies in this thread, including your prior ones, have been helpful. This one wasn’t.

2 Likes

This one would be helpful, if applicable.

Sprinkling App.DoEvents here and there had no effect, and I have no desire to restructure the code to allow the event to return and the cleanup to happen automatically. The issue is well-documented - the Xojo engineers will need to take it from here.

In the meantime, the deprecated (and now missing from the documentation) Graphics.Pixel(x,y) method is fast enough for my uses and does not appear to leak memory.

What’s the issue case number?

Edit: Found https://tracker.xojo.com/xojoinc/xojo/-/issues/75794

On the first message of this thread…

Could you do the processing in a helper? Shared memory to exchange data or if it’s small enough stdin/stdout?

This way; when the helper has completed it’s task, the memory for sure is released. You can also then spin up multiple helpers to help reduce processing time, if it’s taking hours.

For HDRtist I went a different route.

Created a memoryblock and use it as a backing for a CGBitmapContext, then draw the CGImage into the CGBitmapContext. Because you created the CGBitmapConext you can specify the bits per channel and channel order.

I then used a custom plugin (not a Xojo plugin, a CIPlugin) made in Objective-C, which used GCD to process segments on the image, GCD then spread these across all available cores, soaking up all available CPU time until each segment had been processed.

This particular app is one of those mildly annoying projects that we all run into every now and then: mission critical, very limited scope, feels like it shouldn’t take long to put together, and then it takes a week.

In a nutshell, it extracts data from a vendor’s proprietary system by sending keystrokes and mouse clicks to a virtualized Windows instance running the other software. The key and click timings and locations have to be fairly precise, and my app captures sections of the screen from which it retrieves the data. It also uses copy-and-paste to get information out of the software’s interface when possible.

I set it up to do a full scraping run last night - it took about 3 hours to complete. The result is a text file containing 48K rows of key-value data.

The screenshot analysis is what landed me in memory leak land. Because this utility only needs to be run once in a blue moon (basically, any time the vendor updates their software, which is extremely rare), I put together very straightforward code into a button event handler - essentially several lightly nested loops that emit all the commands to the Windows instance while reading the results at the same time.

It took quite a while to get the sequencing and timing of all the commands correct, with a lot of fiddly extra clicking and wait commands to get it just right due to the necessary context switching to make copy/paste work as expected. Analysing the screenshots was the last programming task… so having come this far with my code, I’m unwilling to restructure it to get around a bug since a reasonable workaround is available (using Graphics.Pixel to access image data). Fortunately for me, my screenshots are petite enough and the number of pixel reads small enough for it to be practical.

If this program had a larger scope, I would definitely consider alternate ways of structuring it. For now, it has served its purpose and I’m happy to put it behind me.

As a sidebar: this was one of the most tedious develop-test-fix cycles I’ve ever done. Not only was it tedious to be tweaking the timings of keystrokes and mouse clicks, every time I ran the project I had to dismiss two system security dialogs and make two trips to System Settings to give the debug app permission to send keystrokes and capture screenshots. Every. Single. Time. I appreciate the security but I wish there was a master switch that I could flip to temporarily turn off all the barriers and get my work done.