From 51b1761364b3a2910d5cd48f2af44aa42aeb3b57 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 7 Aug 2018 14:36:21 -0600 Subject: [PATCH] Fix crash during CollectionView thrash --- .../ConversationViewController.m | 44 +++++++++++++------ .../ViewControllers/DebugUI/DebugUIMessages.m | 32 ++++++++++++-- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 775fa736d..4d0fe7a37 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -3402,20 +3402,20 @@ typedef enum : NSUInteger { for (YapDatabaseViewRowChange *rowChange in rowChanges) { switch (rowChange.type) { case YapDatabaseViewChangeDelete: { - DDLogVerbose(@"YapDatabaseViewChangeDelete: %@, %@, %zd", + DDLogVerbose(@"YapDatabaseViewChangeDelete collectionKey: %@, indexPath: %@, finalIndex: %lu", rowChange.collectionKey, rowChange.indexPath, - rowChange.finalIndex); + (unsigned long)rowChange.finalIndex); [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; YapCollectionKey *collectionKey = rowChange.collectionKey; OWSAssert(collectionKey.key.length > 0); break; } case YapDatabaseViewChangeInsert: { - DDLogVerbose(@"YapDatabaseViewChangeInsert: %@, %@, %zd", + DDLogVerbose(@"YapDatabaseViewChangeInsert collectionKey: %@, newIndexPath: %@, finalIndex: %lu", rowChange.collectionKey, rowChange.newIndexPath, - rowChange.finalIndex); + (unsigned long)rowChange.finalIndex); [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; ConversationViewItem *_Nullable viewItem = [self viewItemForIndex:(NSInteger)rowChange.finalIndex]; @@ -3429,19 +3429,20 @@ typedef enum : NSUInteger { break; } case YapDatabaseViewChangeMove: { - DDLogVerbose(@"YapDatabaseViewChangeMove: %@, %@, %@, %zd", + DDLogVerbose(@"YapDatabaseViewChangeMove collectionKey: %@, indexPath: %@, newIndexPath: %@, " + @"finalIndex: %lu", rowChange.collectionKey, rowChange.indexPath, rowChange.newIndexPath, - rowChange.finalIndex); + (unsigned long)rowChange.finalIndex); [self.collectionView moveItemAtIndexPath:rowChange.indexPath toIndexPath:rowChange.newIndexPath]; break; } case YapDatabaseViewChangeUpdate: { - DDLogVerbose(@"YapDatabaseViewChangeUpdate: %@, %@, %zd", + DDLogVerbose(@"YapDatabaseViewChangeUpdate collectionKey: %@, indexPath: %@, finalIndex: %lu", rowChange.collectionKey, rowChange.indexPath, - rowChange.finalIndex); + (unsigned long)rowChange.finalIndex); [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; break; } @@ -3466,16 +3467,31 @@ typedef enum : NSUInteger { [self scrollToBottomAnimated:shouldAnimateScrollToBottom]; } }; + if (shouldAnimateUpdates) { [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; } else { - [UIView performWithoutAnimation:^{ - [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; - if (scrollToBottom) { - [self scrollToBottomAnimated:NO]; - } - }]; + // HACK: We use `UIView.animateWithDuration:0` rather than `UIView.performWithAnimation` to work around a UIKit + // Crash like: + // + // *** Assertion failure in -[ConversationViewLayout prepareForCollectionViewUpdates:], + // /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UICollectionViewLayout.m:760 + // *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'While + // preparing update a visible view at {length = 2, path = 0 - 142} wasn't + // found in the current data model and was not in an update animation. This is an internal error.' + // + // I'm unclear if this is a bug in UIKit, or if we're doing something crazy in + // ConversationViewLayout#prepareLayout. To reproduce, rapidily insert and delete items into the conversation. + // See `DebugUIMessages#thrashCellsInThread:` + [UIView animateWithDuration:0.0 + animations:^{ + [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; + if (scrollToBottom) { + [self scrollToBottomAnimated:shouldAnimateUpdates]; + } + }]; } + self.lastReloadDate = [NSDate new]; } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 8fa6058dd..c724919fd 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -85,10 +85,12 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:^{ [DebugUIMessages deleteAllMessagesInThread:thread]; }]]; - [items addObject:[OWSTableItem itemWithTitle:@"👷 Send All Contact Shares" + [items addObject:[OWSTableItem itemWithTitle:@"👷 Thrash insert/deletes" actionBlock:^{ - [DebugUIMessages sendAllContacts:thread]; + [DebugUIMessages thrashInsertAndDeleteForThread:(TSThread *)thread + counter:300]; }]]; + [items addObjectsFromArray:[self itemsForActions:@[ [DebugUIMessages fakeAllContactShareAction:thread], [DebugUIMessages sendMessageVariationsAction:thread], @@ -126,6 +128,10 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:^{ [DebugUIMessages selectSendMediaAction:thread]; }], + [OWSTableItem itemWithTitle:@"Send All Contact Shares" + actionBlock:^{ + [DebugUIMessages sendAllContacts:thread]; + }], [OWSTableItem itemWithTitle:@"Select Quoted Reply" actionBlock:^{ [DebugUIMessages selectQuotedReplyAction:thread]; @@ -3111,7 +3117,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac OWSAssert(thread); return - [DebugUIMessagesGroupAction allGroupActionWithLabel:@"👷 All Fake Contact Shares" + [DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Contact Shares" subactions:[self allFakeContactShareActions:thread includeLabels:YES]]; } @@ -3700,6 +3706,26 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac } } ++ (void)thrashInsertAndDeleteForThread:(TSThread *)thread counter:(NSUInteger)counter +{ + if (counter == 0) { + return; + } + uint32_t sendDelay = arc4random_uniform((uint32_t)(0.01 * NSEC_PER_SEC)); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, sendDelay), dispatch_get_main_queue(), ^{ + [self sendFakeMessages:1 thread:thread]; + }); + + uint32_t deleteDelay = arc4random_uniform((uint32_t)(0.01 * NSEC_PER_SEC)); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, deleteDelay), dispatch_get_main_queue(), ^{ + [OWSPrimaryStorage.sharedManager.dbReadWriteConnection + asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self deleteRandomMessages:1 thread:thread transaction:transaction]; + }]; + [self thrashInsertAndDeleteForThread:thread counter:counter - 1]; + }); +} + // TODO: Remove. + (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread