-
Notifications
You must be signed in to change notification settings - Fork 1.3k
MGLMapSnapshotter not thread-safe, can cause crashes #11827
Comments
Oops forgot to tag @ivovandongen too. |
During the original implementation of this feature, we went back and forth about a) whether a single MGLMapSnapshotter can be used for multiple requests and b) whether those requests and happen in parallel.
This sounds fine to me, as long as we’re sure that the developer can safely restart the snapshotter on a different queue and/or with a different completion handler, as the |
As a mitigating factor, our docs state: "You can change the snapshotter’s options at any time and reuse the snapshotter for multiple distinct snapshots; however, the snapshotter can only generate one snapshot at a time. If you need to generate multiple snapshots concurrently, create multiple snapshotter objects." And also: "Once you call If developers follow both of those rules and the additional rule "don't touch Because the example just runs a snapshot once, and because that's a common case, it's reasonable to think that many users of the snapshotter won't be affected by these problems. But... 1.) If a developer does re-use the snapshotter, this is a very easy trap to fall into (and the crashes are rare enough they probably wouldn't see them until production). |
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self.
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self.
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self.
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self.
This should be fixed in |
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self. # Conflicts: # platform/darwin/src/MGLMapSnapshotter.mm
- Biggest change: when we apply the watermark on a background thread, don't capture self (turn most of the related instance methods into class methods) - Don't call mbglMapSnapshotter->snapshot from a user-provided queue, since it's an asynchronous call anyway and starting it on the user's queue requires capturing self. # Conflicts: # platform/darwin/src/MGLMapSnapshotter.mm
MGLMapSnapshotter does image generation work on a background thread, using a pointer to
self
captured from the foreground:mapbox-gl-native/platform/darwin/src/MGLMapSnapshotter.mm
Lines 177 to 189 in 60ac08a
Unsynchronized access to MGLMapSnapshotter from multiple threads is unsafe, and can cause memory corruption. We have received crash reports showing stack traces consistent with memory corruption in this code block. We shouldn't need to capture
self
at all here: we should re-factor the block to capture everything it needs by value.There's another
self
capture in a dispatch_async to a user-provided queue here:mapbox-gl-native/platform/darwin/src/MGLMapSnapshotter.mm
Lines 299 to 302 in 60ac08a
This should be easy to clean up -- we just need to copy the scale and make sure the
pointForFn
function is copied cleanly.When we take the snapshot, we capture self and call it in a block from a user-defined queue (we don't control which thread it runs on). We've seen a crash stack trace here that matches the scenario "
_mbglMapSnapshotter
reset on main thread while async block still depends on it":mapbox-gl-native/platform/darwin/src/MGLMapSnapshotter.mm
Lines 148 to 151 in 60ac08a
The fix here is even easier: there's no reason we need to be using
dispatch_async
at all, since_mbglMapSnapshotter->snapshot
is already asynchronous (it kicks off a render on a backgrpund thread, and it passes in anActorRef
for the callback, which is designed to tolerate the foreground resources being reset). I don't understand why this was put in adispatch_async
block in the first place -- I may be missing something, but my guess is that it was a misunderstanding of either howsnapshot
worked or how the callback worked.Incidentally, the snapshot callback is set up to run on the main thread, which should make it OK to capture/use self (if the lifetime of the
MGLMapSnapshotter
ends, the callback will be cancelled and the weak self pointer won't be used; if it is used it will still be live and it will be used on the same thread):mapbox-gl-native/platform/darwin/src/MGLMapSnapshotter.mm
Line 122 in 60ac08a
The important thing is that the user-provided completion callback is run on the user-provided queue -- which we're already doing.
With these changes, cancellation should leave the
MGLMapSnapshotter
in a valid state. I just noticed in the documentation that we say "Once you call this method, you cannot resume the snapshot. In order to obtain the snapshot, create a new MGLMapSnapshotter object." The "cannot resume" limitation will be gone -- if you want, you can re-start the snapshotter. It seems like a weird interface -- I'm not sure why you'd want to stop/restart a snapshot with the exact same options, and if you did I think the natural interface would be to destroy the snapshotter and then re-recreate it (which accomplishes the same thing). But I assume we'll want to maintain this interface since it's already public./cc @lilykaiser @julianrex @friedbunny @1ec5
The text was updated successfully, but these errors were encountered: