Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios, macos] Fix memory leaks in MGLMapSnapshotter. #11133

Merged
merged 1 commit into from
Feb 12, 2018

Conversation

fabian-guerra
Copy link
Contributor

@fabian-guerra fabian-guerra commented Feb 6, 2018

Fixes #11117
WIP

@fabian-guerra fabian-guerra self-assigned this Feb 6, 2018
@fabian-guerra fabian-guerra force-pushed the fabian-snapshotter-leaks-11117 branch 5 times, most recently from ffcadd9 to 8ebc67b Compare February 9, 2018 19:38
Copy link
Contributor Author

@fabian-guerra fabian-guerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @1ec5

@@ -70,7 +70,7 @@ @implementation MGLAttributionInfo
documentAttributes:nil];
#endif

NSMutableArray *infos = [NSMutableArray array];
__block NSMutableArray *infos = [NSMutableArray array];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason besides the fact to highlight it's use in the following block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__block is only needed if the block modifies what infos points to – that is, it modifies not only the array but which array it points to. You’d see a compiler warning if __block is missing and necessary.

@@ -81,7 +81,7 @@ - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate
@end

@interface MGLMapSnapshotter()

@property (nonatomic) BOOL loading;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loading is called inside the lambda that's why I exposed it as property to call weakSelf.loading

dispatch_async(queue, ^{
_snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
_loading = false;
__weak __typeof__(self) weakSelf = self;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were capturing strong references before due to calling self.

});
} else {
// This code is in place to test the _snapshotCallback release cycle.
dispatch_async(queue, ^{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this code snippet to try to isolate GL's snapshotter generation. This way I detected that _snapshotterCall was not getting released. My hypothesis is that an object that is called before the lambda contains the code that is retaining the remaining memory.

}

_attributionInfo = infos;
// [weakSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for snapshotter used to be part of this method. I move it to it's own method to isolate each memory leak that I detected.

@@ -442,4 +461,9 @@ - (void)setOptions:(MGLMapSnapshotOptions *)options
_mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(*mbglFileSource, *_mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds);
}

- (void)dealloc {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for debug purposed only. I will remove it.

@@ -33,12 +33,19 @@ - (void)viewDidLoad {

// Start snapshotters
topLeftSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTL coordinates:CLLocationCoordinate2DMake(37.7184, -122.4365)];
topCenterSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTM coordinates:CLLocationCoordinate2DMake(38.8936, -77.0146)];
topRightSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTR coordinates:CLLocationCoordinate2DMake(-13.1356, -74.2442)];
// This code was commented to isolate the creating of a single snapshot.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented this code to use a single snapshotter instance to tes.

@@ -50,12 +57,13 @@ - (MGLMapSnapshotter*) startSnapshotterForImageView:(UIImageView*) imageView coo
options.zoomLevel = 10;

// Create and start the snapshotter
__weak UIImageView *weakImageView = imageView;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a leak here because we were retaining self when assigning the image to the imageView.

bottomLeftSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBL coordinates:CLLocationCoordinate2DMake(52.5072, 13.4247)];
bottomCenterSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBM coordinates:CLLocationCoordinate2DMake(60.2118, 24.6754)];
bottomRightSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBR coordinates:CLLocationCoordinate2DMake(31.2780, 121.4286)];
- (void)viewWillDisappear:(BOOL)animated
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is for testing.

// Dispatch result to origin queue
dispatch_async(queue, ^{
MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
_snapshotCallback = NULL;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is forcing callback release. I tried first calling _snapshotCallback.reset() but didn't work. Setting the unique pointer to NULL seems more effective.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this statement necessary? _snapshotCallback is already nilled out above, in the completion handler passed into -drawAttributedSnapshot:snapshotImage:pointForFn:queue:completionHandler:. Preferably, this method would be free of side effects related to self.

@ivovandongen
Copy link
Contributor

@fabian-guerra Checking the memory usage on a device, it seems pretty much stable when adding a single release to master:

--- a/platform/darwin/src/MGLMapSnapshotter.mm
+++ b/platform/darwin/src/MGLMapSnapshotter.mm
@@ -284,6 +284,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
                     // Dispatch result to origin queue
                     dispatch_async(queue, ^{
                         MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
+                        _snapshotCallback.reset();
                         completion(snapshot, nil);
                     });
                 });

screen shot 2018-02-12 at 19 21 21

However, the simulator seems to give different results. Is that also what you see?

@fabian-guerra
Copy link
Contributor Author

@ivovandongen I have seen that behavior. Using instruments I can actually see how much memory keeps around. I'm using my iPhone 6+ to test.

instruments_snapshotter

@fabian-guerra fabian-guerra force-pushed the fabian-snapshotter-leaks-11117 branch from 8ebc67b to b1a8731 Compare February 12, 2018 18:56
@@ -70,7 +70,7 @@ @implementation MGLAttributionInfo
documentAttributes:nil];
#endif

NSMutableArray *infos = [NSMutableArray array];
__block NSMutableArray *infos = [NSMutableArray array];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__block is only needed if the block modifies what infos points to – that is, it modifies not only the array but which array it points to. You’d see a compiler warning if __block is missing and necessary.

@@ -117,180 +118,190 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot

_loading = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, get and set loading via the property getter and setter instead of digging into the underlying ivar.

_loading = false;
__weak __typeof__(self) weakSelf = self;
_snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
weakSelf.loading = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lambda uses weakSelf multiple times, with interspersed logic. This introduces a race condition: self could be freed at any time during the lambda’s execution, leading to undefined behavior. weakSelf should generally be accompanied by a strongSelf at the top of the lambda or block that strongly (but temporarily) captures the object. The only exception is for trivial blocks where weakSelf is used only once and the result of the statement is discarded.

// Dispatch result to origin queue
dispatch_async(queue, ^{
MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
_snapshotCallback = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this statement necessary? _snapshotCallback is already nilled out above, in the completion handler passed into -drawAttributedSnapshot:snapshotImage:pointForFn:queue:completionHandler:. Preferably, this method would be free of side effects related to self.

@friedbunny friedbunny added bug iOS Mapbox Maps SDK for iOS performance Speed, stability, CPU usage, memory usage, or power usage labels Feb 12, 2018
@fabian-guerra fabian-guerra force-pushed the fabian-snapshotter-leaks-11117 branch 2 times, most recently from df95622 to 8ba30fb Compare February 12, 2018 21:12
_loading = false;
__weak __typeof__(self) weakSelf = self;
_snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
__typeof__(self) snapshotter = weakSelf;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, the usual pattern is to call this variable strongSelf, which sounds similar to weakSelf.

#if TARGET_OS_IPHONE
CGFloat fontSize = [UIFont smallSystemFontSize];
UIColor *attributeFontColor = [UIColor blackColor];
MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:weakSelf.options.scale];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use snapshotter (or strongSelf) instead of weakSelf throughout this lambda. Otherwise, snapshotter only strongly captures the snapshotter object for as long as that variable is needed within this lambda.

@fabian-guerra fabian-guerra force-pushed the fabian-snapshotter-leaks-11117 branch from 8ba30fb to ea7082a Compare February 12, 2018 21:29
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚰🚯

@fabian-guerra fabian-guerra merged commit 024e75b into release-agua Feb 12, 2018
@fabian-guerra fabian-guerra deleted the fabian-snapshotter-leaks-11117 branch February 12, 2018 21:47
@lilykaiser lilykaiser added this to the ios-v3.7.4 milestone Feb 20, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug iOS Mapbox Maps SDK for iOS performance Speed, stability, CPU usage, memory usage, or power usage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants