From 2b08c1d7bf9e867d6450feb6a246ce36dcdf2cd3 Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Wed, 18 Sep 2019 18:13:07 -0400 Subject: [PATCH] [ios, macos] Fix MGLOfflinePack invalidate crash (#15582) --- platform/darwin/src/MGLOfflinePack.mm | 11 +- platform/darwin/src/MGLOfflineStorage.mm | 4 + ...linePackTests.m => MGLOfflinePackTests.mm} | 18 ++ .../darwin/test/MGLOfflineStorageTests.mm | 198 +++++++++++++++++- .../darwin/test/MGLTestAssertionHandler.h | 18 ++ .../darwin/test/MGLTestAssertionHandler.m | 77 +++++++ .../include/mbgl/storage/offline_database.hpp | 1 + .../src/mbgl/storage/offline_database.cpp | 9 + platform/ios/CHANGELOG.md | 1 + platform/ios/ios.xcodeproj/project.pbxproj | 16 +- platform/macos/CHANGELOG.md | 1 + .../macos/macos.xcodeproj/project.pbxproj | 14 +- 12 files changed, 354 insertions(+), 14 deletions(-) rename platform/darwin/test/{MGLOfflinePackTests.m => MGLOfflinePackTests.mm} (73%) create mode 100644 platform/darwin/test/MGLTestAssertionHandler.h create mode 100644 platform/darwin/test/MGLTestAssertionHandler.m diff --git a/platform/darwin/src/MGLOfflinePack.mm b/platform/darwin/src/MGLOfflinePack.mm index 0f2e8180fa5..5f4ae9c838e 100644 --- a/platform/darwin/src/MGLOfflinePack.mm +++ b/platform/darwin/src/MGLOfflinePack.mm @@ -135,10 +135,15 @@ - (void)suspend { - (void)invalidate { MGLLogInfo(@"Invalidating pack."); MGLAssert(_state != MGLOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack."); + MGLAssert(self.mbglOfflineRegion, @"Should have a valid region"); - self.state = MGLOfflinePackStateInvalid; - _mbglFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); - self.mbglOfflineRegion = nil; + @synchronized (self) { + self.state = MGLOfflinePackStateInvalid; + if (self.mbglOfflineRegion) { + _mbglFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); + } + self.mbglOfflineRegion = nil; + } } - (void)setState:(MGLOfflinePackState)state { diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index 93f986a518e..32d1735bc0c 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -426,11 +426,15 @@ - (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackR - (void)_removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion { mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion; + [pack invalidate]; + if (!mbglOfflineRegion) { + MGLAssert(pack.state == MGLOfflinePackStateInvalid, @"State should be invalid"); completion(nil); return; } + _mbglFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) { NSError *error; if (exception) { diff --git a/platform/darwin/test/MGLOfflinePackTests.m b/platform/darwin/test/MGLOfflinePackTests.mm similarity index 73% rename from platform/darwin/test/MGLOfflinePackTests.m rename to platform/darwin/test/MGLOfflinePackTests.mm index a33665ff0a9..6b454ee8ca0 100644 --- a/platform/darwin/test/MGLOfflinePackTests.m +++ b/platform/darwin/test/MGLOfflinePackTests.mm @@ -1,5 +1,7 @@ #import #import +#import "MGLOfflinePack_Private.h" +#import "MGLTestAssertionHandler.h" @interface MGLOfflinePackTests : XCTestCase @@ -18,6 +20,22 @@ - (void)testInvalidation { XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, MGLInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being suspended."); } +- (void)testInvalidatingAnInvalidPack { + MGLOfflinePack *invalidPack = [[MGLOfflinePack alloc] init]; + + XCTAssertThrowsSpecificNamed([invalidPack invalidate], NSException, NSInternalInconsistencyException, @"Invalid offline pack should raise an exception when being invalidated."); + + // Now try again, without asserts + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self]; + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + + // Make sure this doesn't crash without asserts + [invalidPack invalidate]; + + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; +} + - (void)testProgressBoxing { MGLOfflinePackProgress progress = { .countOfResourcesCompleted = 3, diff --git a/platform/darwin/test/MGLOfflineStorageTests.mm b/platform/darwin/test/MGLOfflineStorageTests.mm index ee4bcc2c65c..b44d4c51cd0 100644 --- a/platform/darwin/test/MGLOfflineStorageTests.mm +++ b/platform/darwin/test/MGLOfflineStorageTests.mm @@ -1,15 +1,16 @@ #import +#import #import "MGLOfflineStorage_Private.h" #import "NSBundle+MGLAdditions.h" #import "NSDate+MGLAdditions.h" - -#import +#import "MGLTestAssertionHandler.h" #include #pragma clang diagnostic ignored "-Wshadow" + @interface MGLOfflineStorageTests : XCTestCase @end @@ -34,7 +35,7 @@ + (void)tearDown { - (void)setUp { [super setUp]; - + static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ XCTestExpectation *expectation = [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { @@ -310,6 +311,197 @@ - (void)testRemovePack { XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack."); } +- (void)addPacks:(NSInteger)count { + + XCTestExpectation *expectation = [self expectationWithDescription:@"added packs"]; + + NSURL *styleURL = [MGLStyle lightStyleURLWithVersion:8]; + + MGLCoordinateBounds bounds[] = { + {{51.5, -0.2}, {51.6, -0.1}}, // London + {{60.1, 24.8}, {60.3, 25.1}}, // Helsinki + {{38.9, -77.1}, {38.9, -77.0}}, // DC + {{37.7, -122.5}, {37.9, -122.4}} // SF + }; + + int arraySize = sizeof(bounds)/sizeof(bounds[0]); + + count = MIN(count, arraySize); + + dispatch_group_t group = dispatch_group_create(); + + for (int i = 0; i < count; i++) { + + dispatch_group_enter(group); + MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds[i] fromZoomLevel:20 toZoomLevel:20]; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ + @"index": @(i) + }]; + + [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region + withContext:context + completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) { + XCTAssertNotNil(pack); + XCTAssertNil(error); + + dispatch_group_leave(group); + }]; + } + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + + [self waitForExpectations:@[expectation] timeout:1.0]; +} + +- (void)testRemovePackTwiceInSuccession { + + [self addPacks:1]; + + NSUInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count; + + MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs.lastObject; + XCTAssertNotNil(pack, @"Added pack should still exist."); + + [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { + const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); + NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; + return changeKind == NSKeyValueChangeRemoval && indices.count == 1; + }]; + + XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"]; + + [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:nil]; + + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self]; + + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + + [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { + XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler."); + [completionHandlerExpectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; + + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + + XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should have been invalidated synchronously."); + + XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack."); + + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); +} + +- (void)test15536RemovePacksWhileReloading { + + // This test triggers + // + // throw std::runtime_error("Malformed offline region definition"); + // + // in offline.cpp + // + // Reloading packs, while trying to remove them is currently problematic. + + [self addPacks:4]; + + NSInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count; + XCTAssert(countOfPacks > 0); + + // Now delete packs one by one + XCTestExpectation *expectation = [self expectationWithDescription:@"All packs removed"]; + expectation.expectedFulfillmentCount = countOfPacks; + + MGLOfflineStorage *storage = [MGLOfflineStorage sharedOfflineStorage]; + NSArray *packs = [storage.packs copy]; + + // Simulate what happens the first time sharedOfflineStorage is accessed + [storage reloadPacks]; + + NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + MGLOfflinePack *pack = (MGLOfflinePack*)evaluatedObject; + return pack.state != MGLOfflinePackStateInvalid; + }]]; + + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self]; + + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + + for (MGLOfflinePack *pack in validPacks) { + [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { + [expectation fulfill]; + }]; + } + + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + + [self waitForExpectations:@[expectation] timeout:10.0]; + + // TODO: What should we expect here? All packs removed? + + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); +} + +// Test to explore https://github.com/mapbox/mapbox-gl-native/issues/15536 +- (void)test15536RemovePacksOnBackgroundQueueWhileReloading { + + [self addPacks:4]; + + NSInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count; + XCTAssert(countOfPacks > 0); + + // Now delete packs one by one + dispatch_queue_t queue = dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL); + + XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"]; + expectation.expectedFulfillmentCount = countOfPacks; + + MGLOfflineStorage *storage = [MGLOfflineStorage sharedOfflineStorage]; + + // Simulate what happens the first time sharedOfflineStorage is accessed + [storage reloadPacks]; + +// NSArray *packs = [storage.packs copy]; + + dispatch_async(queue, ^{ + NSArray *packs = storage.packs; + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self]; + + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + + NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + MGLOfflinePack *pack = (MGLOfflinePack*)evaluatedObject; + return pack.state != MGLOfflinePackStateInvalid; + }]]; + + for (MGLOfflinePack *pack in validPacks) { + // NOTE: pack can be invalid, as we have two threads potentially + // modifying the same MGLOfflinePack. + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { + dispatch_group_leave(group); + }]; + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + [expectation fulfill]; + } + + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + }); + + [self waitForExpectations:@[expectation] timeout:60.0]; + + // TODO: What should we expect here? All packs removed? + + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); +} + - (void)testCountOfBytesCompleted { XCTAssertGreaterThan([MGLOfflineStorage sharedOfflineStorage].countOfBytesCompleted, 0UL); } diff --git a/platform/darwin/test/MGLTestAssertionHandler.h b/platform/darwin/test/MGLTestAssertionHandler.h new file mode 100644 index 00000000000..f1aa39921e3 --- /dev/null +++ b/platform/darwin/test/MGLTestAssertionHandler.h @@ -0,0 +1,18 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// Use to catch or log assertions that occur in dispatch blocks, timers or +// other asynchronous operations. +@interface MGLTestAssertionHandler : NSAssertionHandler + +- (instancetype)initWithTestCase:(XCTestCase *)testCase; +@property (nonatomic, weak) XCTestCase *testCase; + +// If YES, use `_XCTPreformattedFailureHandler` to "fail" the test, +// otherwise log the assert. +@property (nonatomic) BOOL shouldFail; +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/test/MGLTestAssertionHandler.m b/platform/darwin/test/MGLTestAssertionHandler.m new file mode 100644 index 00000000000..4b504427b5f --- /dev/null +++ b/platform/darwin/test/MGLTestAssertionHandler.m @@ -0,0 +1,77 @@ +#import "MGLTestAssertionHandler.h" + +@implementation MGLTestAssertionHandler + +- (instancetype)initWithTestCase:(XCTestCase *)testCase { + if ((self = [super init])) { + _testCase = testCase; + } + return self; +} + +- (void)handleFailureInMethod:(SEL)selector + object:(id)object + file:(NSString *)fileName + lineNumber:(NSInteger)line + description:(NSString *)format, ... +{ + va_list args; + va_start(args, format); + NSString *description = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + NSString *condition = [NSString stringWithFormat: + @"`[%@ %@]`", + object, NSStringFromSelector(selector) + ]; + + if (self.testCase && self.shouldFail) { + _XCTPreformattedFailureHandler(self.testCase, + YES, + fileName, + line, + condition, + description + ); + } + else { + NSLog(@"Assertion Failure: %@:%lu: %@ - %@", + fileName, + line, + condition, + description); + } +} + +- (void)handleFailureInFunction:(NSString *)functionName + file:(NSString *)fileName + lineNumber:(NSInteger)line + description:(NSString *)format, ... +{ + va_list args; + va_start(args, format); + NSString *description = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + NSString *condition = [NSString stringWithFormat: + @"`%@`", + functionName]; + + if (self.testCase && self.shouldFail) { + _XCTPreformattedFailureHandler(self.testCase, + YES, + fileName, + line, + condition, + description); + } + else { + NSLog(@"Assertion Failure: %@:%lu: %@ - %@", + fileName, + line, + condition, + description); + } +} +@end + diff --git a/platform/default/include/mbgl/storage/offline_database.hpp b/platform/default/include/mbgl/storage/offline_database.hpp index e19dcfade9a..96b867eaa63 100644 --- a/platform/default/include/mbgl/storage/offline_database.hpp +++ b/platform/default/include/mbgl/storage/offline_database.hpp @@ -98,6 +98,7 @@ class OfflineDatabase : private util::noncopyable { void initialize(); void handleError(const mapbox::sqlite::Exception&, const char* action); void handleError(const util::IOException&, const char* action); + void handleError(const std::runtime_error& ex, const char* action); void removeExisting(); void removeOldCacheTable(); diff --git a/platform/default/src/mbgl/storage/offline_database.cpp b/platform/default/src/mbgl/storage/offline_database.cpp index 83eea7bcc47..133e1f79928 100644 --- a/platform/default/src/mbgl/storage/offline_database.cpp +++ b/platform/default/src/mbgl/storage/offline_database.cpp @@ -124,6 +124,10 @@ void OfflineDatabase::handleError(const util::IOException& ex, const char* actio Log::Error(Event::Database, ex.code, "Can't %s: %s", action, ex.what()); } +void OfflineDatabase::handleError(const std::runtime_error& ex, const char* action) { + Log::Error(Event::Database, -1, "Can't %s: %s", action, ex.what()); +} + void OfflineDatabase::removeExisting() { Log::Warning(Event::Database, "Removing existing incompatible offline database"); @@ -1059,6 +1063,11 @@ expected OfflineDatabase::getRegion } catch (const mapbox::sqlite::Exception& ex) { handleError(ex, "load region"); return unexpected(std::current_exception()); +} catch (const std::runtime_error& ex) { + // Catch errors from malformed offline region definitions + // and skip them (as above). + handleError(ex, "load region"); + return unexpected(std::current_exception()); } expected OfflineDatabase::getRegionCompletedStatus(int64_t regionID) try { diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 089cc186064..6329771518f 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -23,6 +23,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Enabled use of `MGLSymbolStyleLayer.textOffset` option together with `MGLSymbolStyleLayer.textVariableAnchor` (if `MGLSymbolStyleLayer.textRadialOffset` option is not provided). ([#15542](https://github.com/mapbox/mapbox-gl-native/pull/15542)) * Fixed an issue with the appearance of the compass text in iOS 13. ([#15547](https://github.com/mapbox/mapbox-gl-native/pull/15547)) * Fixed a bug where the completion block passed to `-[MGLMapView flyToCamera:completionHandler:` (and related methods) wouldn't be called. ([#15473](https://github.com/mapbox/mapbox-gl-native/pull/15473)) +* Fixed a crash when `-[MGLOfflinePack invalidate]` is called on different threads. ([#15582](https://github.com/mapbox/mapbox-gl-native/pull/15582)) ### User interaction diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 9ac5d564fe7..20001c26a8a 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -503,6 +503,7 @@ ACD0245B2187EABA00D8C8A7 /* MMEMetricsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD024542187EAAF00D8C8A7 /* MMEMetricsManager.m */; }; ACD0245E2187EACB00D8C8A7 /* MMEMetrics.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD024572187EAAF00D8C8A7 /* MMEMetrics.m */; }; ACD0245F2187EACB00D8C8A7 /* MMEMetrics.m in Sources */ = {isa = PBXBuildFile; fileRef = ACD024572187EAAF00D8C8A7 /* MMEMetrics.m */; }; + CA0B3C022329DE9A00E4B493 /* MGLTestAssertionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAAA65D82321BBA900F08A39 /* MGLTestAssertionHandler.m */; }; CA0C27922076C804001CE5B7 /* MGLShapeSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA0C27912076C804001CE5B7 /* MGLShapeSourceTests.m */; }; CA0C27942076CA19001CE5B7 /* MGLMapViewIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA0C27932076CA19001CE5B7 /* MGLMapViewIntegrationTest.m */; }; CA1B4A512099FB2200EDD491 /* MGLMapSnapshotterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA1B4A502099FB2200EDD491 /* MGLMapSnapshotterTest.m */; }; @@ -523,6 +524,7 @@ CA8FBC0921A47BB100D1203C /* MGLRendererConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8FBC0821A47BB100D1203C /* MGLRendererConfigurationTests.mm */; }; CAA69DA4206DCD0E007279CD /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; }; CAA69DA5206DCD0E007279CD /* Mapbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CAAA65D92321BBA900F08A39 /* MGLTestAssertionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAAA65D82321BBA900F08A39 /* MGLTestAssertionHandler.m */; }; CABE5DAD2072FAB40003AF3C /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; CAD9D0AA22A86D6F001B25EE /* MGLResourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */; }; CAE7AD5520F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */; }; @@ -551,7 +553,7 @@ DA2DBBCE1D51E80400D38FF9 /* MGLStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */; }; DA2E88561CC036F400F24E7B /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */; }; - DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */; }; + DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.mm */; }; DA2E88631CC0382C00F24E7B /* MGLOfflineRegionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */; }; DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */; }; DA35A29E1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A29D1CC9E94C00E826B2 /* MGLCoordinateFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1211,6 +1213,8 @@ CA86FF0D22D8D5A0009EB14A /* MGLNetworkConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfigurationTests.m; sourceTree = ""; }; CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleURLIntegrationTest.m; sourceTree = ""; }; CA8FBC0821A47BB100D1203C /* MGLRendererConfigurationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLRendererConfigurationTests.mm; path = ../../darwin/test/MGLRendererConfigurationTests.mm; sourceTree = ""; }; + CAAA65D72321BBA900F08A39 /* MGLTestAssertionHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MGLTestAssertionHandler.h; path = ../../darwin/test/MGLTestAssertionHandler.h; sourceTree = ""; }; + CAAA65D82321BBA900F08A39 /* MGLTestAssertionHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLTestAssertionHandler.m; path = ../../darwin/test/MGLTestAssertionHandler.m; sourceTree = ""; }; CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLResourceTests.mm; path = ../../darwin/test/MGLResourceTests.mm; sourceTree = ""; }; CAE7AD5320F46EF5003B6782 /* integration-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "integration-Bridging-Header.h"; sourceTree = ""; }; CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MGLMapSnapshotterSwiftTests.swift; sourceTree = ""; }; @@ -1244,7 +1248,7 @@ DA2E88511CC036F400F24E7B /* test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = test.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DA2E88551CC036F400F24E7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLGeometryTests.mm; path = ../../darwin/test/MGLGeometryTests.mm; sourceTree = ""; }; - DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflinePackTests.m; path = ../../darwin/test/MGLOfflinePackTests.m; sourceTree = ""; }; + DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflinePackTests.mm; path = ../../darwin/test/MGLOfflinePackTests.mm; sourceTree = ""; }; DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflineRegionTests.m; path = ../../darwin/test/MGLOfflineRegionTests.m; sourceTree = ""; }; DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLStyleTests.mm; path = ../../darwin/test/MGLStyleTests.mm; sourceTree = ""; }; DA33895F1FA3EAB7001EA329 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Foundation.strings"; sourceTree = ""; }; @@ -1758,6 +1762,8 @@ 4031ACFE1E9FD29F00A3EA26 /* MGLSDKTestHelpers.swift */, A4DE3DCA23038A7F005B3473 /* MGLMockGestureRecognizers.h */, A4DE3DC823038A07005B3473 /* MGLMockGestureRecognizers.m */, + CAAA65D72321BBA900F08A39 /* MGLTestAssertionHandler.h */, + CAAA65D82321BBA900F08A39 /* MGLTestAssertionHandler.m */, ); name = "Test Helpers"; sourceTree = ""; @@ -2084,7 +2090,7 @@ 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */, 96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */, DAE7DEC11E245455007505A6 /* MGLNSStringAdditionsTests.m */, - DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */, + DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.mm */, DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */, 55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */, 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */, @@ -3219,6 +3225,7 @@ CAE7AD5520F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift in Sources */, CA0C27922076C804001CE5B7 /* MGLShapeSourceTests.m in Sources */, 077061DA215DA00E000FEF62 /* MGLTestLocationManager.m in Sources */, + CA0B3C022329DE9A00E4B493 /* MGLTestAssertionHandler.m in Sources */, CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.mm in Sources */, CA4F3BE223107793008BAFEA /* MGLCameraTransitionTests.mm in Sources */, CA4C54FE2324948100A81659 /* MGLSourceTests.swift in Sources */, @@ -3264,6 +3271,7 @@ files = ( A4DE3DCC23038CCA005B3473 /* MGLMockGestureRecognizers.m in Sources */, A4DE3DCB23038C98005B3473 /* MGLMockGestureRecognizers.h in Sources */, + CAAA65D92321BBA900F08A39 /* MGLTestAssertionHandler.m in Sources */, 6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */, DA2E88631CC0382C00F24E7B /* MGLOfflineRegionTests.m in Sources */, 409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */, @@ -3302,7 +3310,7 @@ 9658C155204761FC00D8A674 /* MGLMapViewScaleBarTests.m in Sources */, 409D0A0D1ED614CE00C95D0C /* MGLAnnotationViewIntegrationTests.swift in Sources */, 9686D1BD22D9357700194EA0 /* MGLMapViewZoomTests.mm in Sources */, - DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */, + DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.mm in Sources */, 55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */, 07D8C6FF1F67562C00381808 /* MGLComputedShapeSourceTests.m in Sources */, 920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */, diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 888d4c52671..fc59457a261 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -19,6 +19,7 @@ * Fixed an issue of integer overflow when converting `tileCoordinates` to `LatLon`, which caused issues such as `queryRenderedFeatures` and `querySourceFeatures` returning incorrect coordinates at zoom levels 20 and higher. ([#15560](https://github.com/mapbox/mapbox-gl-native/pull/15560)) * Added an `-[MGLMapSnapshotter startWithOverlayHandler:completionHandler:]` method to provide the snapshot's current `CGContext` in order to perform custom drawing on `MGLMapSnapShot` objects. ([#15530](https://github.com/mapbox/mapbox-gl-native/pull/15530)) * Fixed an issue that `maxzoom` in style `Sources` option was ignored when URL resource is provided. It may cause problems such as extra tiles downloading at higher zoom level than `maxzoom`, or problems that wrong setting of `overscaledZ` in `OverscaledTileID` that will be passed to `SymbolLayout`, leading wrong rendering appearance. ([#15581](https://github.com/mapbox/mapbox-gl-native/pull/15581)) +* Fixed a crash when `-[MGLOfflinePack invalidate]` is called on different threads. ([#15582](https://github.com/mapbox/mapbox-gl-native/pull/15582)) ### Styles and rendering diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 50f592a4bca..226bc623124 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -128,6 +128,8 @@ 9654C12B1FFC38E000DB6A19 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C12A1FFC38E000DB6A19 /* MGLPolyline_Private.h */; }; 9654C12D1FFC394700DB6A19 /* MGLPolygon_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C12C1FFC394700DB6A19 /* MGLPolygon_Private.h */; }; 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027331E57C9A7004B8E66 /* Localizable.strings */; }; + CA0B3C072329F7E700E4B493 /* MGLTestAssertionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CA0B3C052329F7E600E4B493 /* MGLTestAssertionHandler.m */; }; + CA0B3C092329FB4800E4B493 /* MGLOfflinePackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA0B3C082329FB4800E4B493 /* MGLOfflinePackTests.mm */; }; CA4045C7216720D700B356E1 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA4045C4216720D700B356E1 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA8FBC0D21A4A74300D1203C /* MGLRendererConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8FBC0C21A4A74300D1203C /* MGLRendererConfigurationTests.mm */; }; CA9461A620884CCB0015EB12 /* MGLAnnotationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA9461A520884CCB0015EB12 /* MGLAnnotationTests.m */; }; @@ -280,7 +282,6 @@ DAE6C3BF1CC31F2E00DB3429 /* mapbox.pdf in Resources */ = {isa = PBXBuildFile; fileRef = DAE6C3BC1CC31F2E00DB3429 /* mapbox.pdf */; }; DAE6C3C21CC31F4500DB3429 /* Mapbox.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3C11CC31F4500DB3429 /* Mapbox.h */; settings = {ATTRIBUTES = (Public, ); }; }; DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */; }; - DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */; }; DAE6C3D41CC34C9900DB3429 /* MGLOfflineRegionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */; }; DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */; }; DAE7DEC41E24549F007505A6 /* MGLNSStringAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE7DEC31E24549F007505A6 /* MGLNSStringAdditionsTests.m */; }; @@ -467,6 +468,9 @@ 96E027391E57C9B9004B8E66 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; 96E0273A1E57C9BB004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 96E0273B1E57C9BC004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + CA0B3C042329F7E600E4B493 /* MGLTestAssertionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLTestAssertionHandler.h; path = ../../darwin/test/MGLTestAssertionHandler.h; sourceTree = ""; }; + CA0B3C052329F7E600E4B493 /* MGLTestAssertionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLTestAssertionHandler.m; path = ../../darwin/test/MGLTestAssertionHandler.m; sourceTree = ""; }; + CA0B3C082329FB4800E4B493 /* MGLOfflinePackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflinePackTests.mm; path = ../../darwin/test/MGLOfflinePackTests.mm; sourceTree = ""; }; CA4045C4216720D700B356E1 /* MGLCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCluster.h; sourceTree = ""; }; CA8FBC0C21A4A74300D1203C /* MGLRendererConfigurationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLRendererConfigurationTests.mm; path = ../../darwin/test/MGLRendererConfigurationTests.mm; sourceTree = ""; }; CA9461A520884CCB0015EB12 /* MGLAnnotationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLAnnotationTests.m; path = test/MGLAnnotationTests.m; sourceTree = SOURCE_ROOT; }; @@ -693,7 +697,6 @@ DAE6C3C11CC31F4500DB3429 /* Mapbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Mapbox.h; path = src/Mapbox.h; sourceTree = SOURCE_ROOT; }; DAE6C3C61CC3499100DB3429 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLGeometryTests.mm; path = ../../darwin/test/MGLGeometryTests.mm; sourceTree = ""; }; - DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflinePackTests.m; path = ../../darwin/test/MGLOfflinePackTests.m; sourceTree = ""; }; DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflineRegionTests.m; path = ../../darwin/test/MGLOfflineRegionTests.m; sourceTree = ""; }; DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLStyleTests.mm; path = ../../darwin/test/MGLStyleTests.mm; sourceTree = ""; }; DAE7DEC31E24549F007505A6 /* MGLNSStringAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLNSStringAdditionsTests.m; path = ../../darwin/test/MGLNSStringAdditionsTests.m; sourceTree = ""; }; @@ -870,6 +873,8 @@ 4031AD001E9FD61000A3EA26 /* Test Helpers */ = { isa = PBXGroup; children = ( + CA0B3C042329F7E600E4B493 /* MGLTestAssertionHandler.h */, + CA0B3C052329F7E600E4B493 /* MGLTestAssertionHandler.m */, 4031AD011E9FD6A300A3EA26 /* MGLSDKTestHelpers.swift */, ); name = "Test Helpers"; @@ -1195,7 +1200,7 @@ 076171C4213A0DC200668A35 /* MGLMapViewTests.m */, 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */, DAE7DEC31E24549F007505A6 /* MGLNSStringAdditionsTests.m */, - DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */, + CA0B3C082329FB4800E4B493 /* MGLOfflinePackTests.mm */, DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */, 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */, 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */, @@ -1737,6 +1742,7 @@ 1F7454AB1ED1DDBD00021D39 /* MGLLightTest.mm in Sources */, 07A240941F675674002C8210 /* MGLComputedShapeSourceTests.m in Sources */, DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */, + CA0B3C072329F7E700E4B493 /* MGLTestAssertionHandler.m in Sources */, DA695424215B1E6C002041A4 /* MGLMapCameraTests.m in Sources */, 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */, DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, @@ -1752,6 +1758,7 @@ DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */, DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */, DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */, + CA0B3C092329FB4800E4B493 /* MGLOfflinePackTests.mm in Sources */, 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */, 4031ACFC1E9EB3C100A3EA26 /* MGLMapViewDelegateIntegrationTests.swift in Sources */, CA8FBC0D21A4A74300D1203C /* MGLRendererConfigurationTests.mm in Sources */, @@ -1760,7 +1767,6 @@ DAA999011E9F5EC5002E6EA6 /* MGLFillExtrusionStyleLayerTests.mm in Sources */, DA29875A1E1A4290002299F5 /* MGLDocumentationExampleTests.swift in Sources */, 07BA4CAC1EE21887004528F5 /* MGLImageSourceTests.m in Sources */, - DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */, DA87A9A51DCACC5000810D09 /* MGLLineStyleLayerTests.mm in Sources */, DA87A9A31DCACC5000810D09 /* MGLRasterStyleLayerTests.mm in Sources */, CA9461A620884CCB0015EB12 /* MGLAnnotationTests.m in Sources */,