-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] Fix memory leaks in MGLMapSnapshotter. #11133
Conversation
ffcadd9
to
8ebc67b
Compare
There was a problem hiding this 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]; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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, ^{ |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
@fabian-guerra Checking the memory usage on a device, it seems pretty much stable when adding a single release to master:
However, the simulator seems to give different results. Is that also what you see? |
@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. |
8ebc67b
to
b1a8731
Compare
@@ -70,7 +70,7 @@ @implementation MGLAttributionInfo | |||
documentAttributes:nil]; | |||
#endif | |||
|
|||
NSMutableArray *infos = [NSMutableArray array]; | |||
__block NSMutableArray *infos = [NSMutableArray array]; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
.
df95622
to
8ba30fb
Compare
_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; |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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.
8ba30fb
to
ea7082a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚰🚯
Fixes #11117
WIP