Memory allocation behaviour change in createImage() causing massive allocations for filmstrips

Hi @reuk

This commit:

commit 0223e44ae75193015b4fc86aa0e9d4347b62eb2c
Author: reuk reuk@users.noreply.github.com
Date: Thu Feb 10 18:59:38 2022 +0000

Image:  Keep track of contiguous buffer size to avoid heap buffer overflows

Is causing a memory leak on iOS when using filmstrips to render knobs/sliders.
For some reason this memory leak only actually exhibits when running on an actual device and the simulators don’t have the issue.

Seems that:
detail::ImagePtr image { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) };

isn’t getting freed.

thx Lee

Update: This is quite urgent as it’s causing crashes due to the app running out of memory

I’ve tried a bit to reproduce this, but without much luck so far. Some more details would be helpful to track down the issue.

  • How are you rendering the film strips? I’d assume you’re calling ImageCache::getFromMemory to load a filmstrip, Image::getClippedImage to extract the frame that you want to render, and then using Graphics::drawImage to render the clipped region. Is that accurate? Are you creating your own temporary graphics context anywhere?
  • What is your test procedure? I’ve tried putting together a test app that renders lots of clipped images, but the Instruments leak detector doesn’t find anything and Xcode’s built-in memory graph doesn’t seem to climb when left running for a while. I’ve tested on a physical iPad running iOS 15.4, and in the simulator.
  • Have you seen the same issue in any of the JUCE demos?
  • How did you find that the issue is in the linked commit? I ask because I don’t see any obvious changes to the memory management in this commit. Are you certain that the issue is present when building with this commit, but not present when building with the preceding commit?

Hi

  1. Filmstrips are loaded via ImageCache::getFromFile() and the resulting image is cached until required.
  2. I’m not calling getClippedImage(), I’m calling
void Graphics::drawImage (const Image& imageToDraw,
                          int dx, int dy, int dw, int dh,
                          int sx, int sy, int sw, int sh,
                          const bool fillAlphaChannelWithCurrentBrush) const

which internally clips the image.

  1. My test procedure is to step over the above call whilst watching the memory usage. Before the particular checkin, and when running on mac or in simulator, the memory hardly changes. After the change I’m getting about 10M of memory allocated per call
  2. No, but then I haven’t been building any to test this situation specifically
  3. I pulled 6.1.5 and determined the issue wasn’t there. I then pulled 6.1.6 and started working backwards until the problem stopped occurring. I tested repeatedly to make sure this was correct and before this commit my app didn’t crash and stepping over the draw image call showed no extra memory usage, after it did.

thx

Just checked again:

git checkout 0223e44ae75193015b4fc86aa0e9d4347b62eb2c

Memory leak and crash on iOS

git checkout ec867690b7fb4a8dde011ec5a929e2ed22fda74b

No problems.

I’ll see if I can narrow down when the allocation occurs.

ok, so looks like maybe the new size field is the problem - this isn’t a memory leak, just a massive allocation which is why I’m seeing it in one circumstance and not the other.

So, old code in createImage() does this:

CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr,
                                                       (const UInt8*) srcData.data,
                                                       (CFIndex) ((size_t) srcData.lineStride * (size_t) srcData.height)));

and new code does this:

CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr, (const UInt8*) srcData.data, (CFIndex) srcData.size));

using the new size field, however this is what is being passed in:

so instead of allocating 1888 * 561 bytes, it’s now allocating 135M ( 1888 * 71808 ) and the app is quickly running out of memory. 71808 is the height of the filmstrip.

So this functions behaviour has gone from allocating memory for the frame, to allocating the memory for the whole filmstrip. Is this the intended behaviour of this change or is this a bug?

So, looking at this I think the reason this is crashing on iOS on the ipad is because it’s running out of memory. The same problem would happen on other platforms if they didn’t have so much memory.

It seems strange that when we’re clipping a filmstrip and then working on it, the memory allocated for the image is still the filmstrip and not the clipped image - perhaps this needs some adjustment so the the “size” is correct for the clipped image?

thx

Thanks, that’s helpful. The idea was to keep track of the available bitmap size, because in some cases “dataPtr + height * linestride” could end up pointing past the end of the allocated buffer. It sounds like allocating enough room for the entire rest of the image is unnecessary, though.

Please could you try changing

CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr, (const UInt8*) srcData.data, (CFIndex) srcData.size));

to

const auto idealSize = (size_t) srcData.lineStride * (size_t) srcData.height;
const auto usableSize = jmin (idealSize, srcData.size);
CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr, (const UInt8*) srcData.data, (CFIndex) usableSize));

and check whether it resolves the problem? Thanks!

2 Likes

yup, all sorted - for filmstrips essentially goes back to the previous behaviour - thanks!

Cool, thanks for trying that out. I’ll try to get this on develop shortly.

1 Like

One other question here - the filmstrip file is 8M, why is 135M being allocated to store it internally in the app? thx

Presumably your file is a compressed image (png, jpeg etc.) wheras the CoreGraphics image data is uncompressed.

1 Like

ah yes, of course :slight_smile:

1 Like

thx - all good now.

There was another recent behaviour change which I noted here:

Easily fixed by supplying an audio config to the MIDI FX plugin, but still wanted to make sure you were aware of this requirement now. Cheers