Avoid crashing on startup due to database view creation.

* Substitute unread view for unseen view until unseen view is ready.
* Register as many views as possible async.
* Perform blocking, safe migrations before async registration of views.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent bbc7c44c93
commit 0f96341059

@ -193,7 +193,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<id<OWSReadTracking>> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray<id<OWSReadTracking>> *messages = [NSMutableArray new];
[[transaction ext:TSUnseenDatabaseViewExtensionName]
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateRowsInGroup:self.uniqueId
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {

@ -95,7 +95,7 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
[interactionsToMarkAsRead addObject:message];
[self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[transaction ext:TSUnseenDatabaseViewExtensionName]
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateRowsInGroup:message.uniqueThreadId
usingBlock:^(NSString *collection,
NSString *key,

@ -11,6 +11,7 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange = @"kNSNotificationName_BlockedPhoneNumbersDidChange";
NSString *const kOWSBlockingManager_BlockedPhoneNumbersCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
// This key is used to persist the current "blocked phone numbers" state.
NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey";

@ -1,9 +1,5 @@
//
// TSDatabaseSecondaryIndexes.m
// Signal
//
// Created by Frederic Jacobs on 26/01/15.
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSDatabaseSecondaryIndexes.h"
@ -43,4 +39,5 @@
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[[transaction ext:@"idx"] enumerateKeysMatchingQuery:query usingBlock:block];
}
@end

@ -5,36 +5,53 @@
#import <Foundation/Foundation.h>
#import <YapDatabase/YapDatabaseViewTransaction.h>
extern NSString *const kNSNotificationName_DatabaseViewRegistrationComplete;
extern NSString *const TSInboxGroup;
extern NSString *const TSArchiveGroup;
extern NSString *const TSUnreadIncomingMessagesGroup;
extern NSString *const TSSecondaryDevicesGroup;
extern NSString *const TSThreadDatabaseViewExtensionName;
extern NSString *const TSMessageDatabaseViewExtensionName;
extern NSString *const TSUnreadDatabaseViewExtensionName;
extern NSString *const TSSecondaryDevicesDatabaseViewExtensionName;
@interface TSDatabaseView : NSObject
extern NSString *TSInboxGroup;
extern NSString *TSArchiveGroup;
extern NSString *TSUnreadIncomingMessagesGroup;
extern NSString *TSSecondaryDevicesGroup;
- (instancetype)init NS_UNAVAILABLE;
extern NSString *TSThreadDatabaseViewExtensionName;
extern NSString *TSMessageDatabaseViewExtensionName;
extern NSString *TSThreadOutgoingMessageDatabaseViewExtensionName;
extern NSString *TSUnreadDatabaseViewExtensionName;
extern NSString *TSUnseenDatabaseViewExtensionName;
extern NSString *TSThreadSpecialMessagesDatabaseViewExtensionName;
extern NSString *TSSecondaryDevicesDatabaseViewExtensionName;
+ (BOOL)hasPendingViewRegistrations;
+ (BOOL)registerThreadDatabaseView;
+ (BOOL)registerThreadInteractionsDatabaseView;
+ (BOOL)registerThreadOutgoingMessagesDatabaseView;
+ (void)registerThreadDatabaseView;
+ (void)registerThreadInteractionsDatabaseView;
+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView;
// Instances of OWSReadTracking for wasRead is NO and shouldAffectUnreadCounts is YES.
//
// Should be used for "unread message counts".
+ (BOOL)registerUnreadDatabaseView;
+ (void)registerUnreadDatabaseView;
// Should be used for "unread indicator".
//
// Instances of OWSReadTracking for wasRead is NO.
+ (BOOL)registerUnseenDatabaseView;
+ (void)asyncRegisterUnseenDatabaseView;
+ (void)asyncRegisterThreadSpecialMessagesDatabaseView;
+ (BOOL)registerThreadSpecialMessagesDatabaseView;
+ (void)asyncRegisterSecondaryDevicesDatabaseView;
// Returns the "unseen" database view if it is ready;
// otherwise it returns the "unread" database view.
+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction;
// NOTE: It is not safe to call this method until hasPendingViewRegistrations is YES.
+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction;
// NOTE: It is not safe to call this method until hasPendingViewRegistrations is YES.
+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction;
@end

@ -12,32 +12,88 @@
#import "TSThread.h"
#import <YapDatabase/YapDatabaseView.h>
NSString *TSInboxGroup = @"TSInboxGroup";
NSString *TSArchiveGroup = @"TSArchiveGroup";
NSString *const kNSNotificationName_DatabaseViewRegistrationComplete =
@"kNSNotificationName_DatabaseViewRegistrationComplete";
NSString *TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup";
NSString *TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup";
NSString *const TSInboxGroup = @"TSInboxGroup";
NSString *const TSArchiveGroup = @"TSArchiveGroup";
NSString *TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName";
NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName";
NSString *TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName";
NSString *TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName";
NSString *TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName";
NSString *TSThreadSpecialMessagesDatabaseViewExtensionName = @"TSThreadSpecialMessagesDatabaseViewExtensionName";
NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName";
NSString *const TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup";
NSString *const TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup";
NSString *const TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName";
NSString *const TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName";
NSString *const TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName";
NSString *const TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName";
NSString *const TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName";
NSString *const TSThreadSpecialMessagesDatabaseViewExtensionName = @"TSThreadSpecialMessagesDatabaseViewExtensionName";
NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName";
@interface TSDatabaseView ()
@property (nonatomic) int pendingViewRegistrations;
@end
#pragma mark -
@implementation TSDatabaseView
+ (BOOL)registerMessageDatabaseViewWithName:(NSString *)viewName
+ (instancetype)sharedInstance
{
static TSDatabaseView *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initDefault];
});
return sharedInstance;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSSingletonAssert();
return self;
}
- (void)setPendingViewRegistrations:(int)pendingViewRegistrations
{
OWSAssert([NSThread isMainThread]);
_pendingViewRegistrations = pendingViewRegistrations;
if (pendingViewRegistrations == 0) {
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_DatabaseViewRegistrationComplete
object:nil
userInfo:nil];
}
}
+ (BOOL)hasPendingViewRegistrations
{
OWSAssert([NSThread isMainThread]);
return [TSDatabaseView sharedInstance].pendingViewRegistrations > 0;
}
+ (void)registerMessageDatabaseViewWithName:(NSString *)viewName
viewGrouping:(YapDatabaseViewGrouping *)viewGrouping
version:(NSString *)version
async:(BOOL)async
{
OWSAssert([NSThread isMainThread]);
OWSAssert(viewName.length > 0);
OWSAssert((viewGrouping));
YapDatabaseView *existingView = [[TSStorageManager sharedManager].database registeredExtension:viewName];
if (existingView) {
return YES;
return;
}
YapDatabaseViewSorting *viewSorting = [self messagesSorting];
@ -50,10 +106,27 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
YapDatabaseView *view =
[[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:version options:options];
return [[TSStorageManager sharedManager].database registerExtension:view withName:viewName];
if (async) {
TSDatabaseView.sharedInstance.pendingViewRegistrations++;
[[TSStorageManager sharedManager].database
asyncRegisterExtension:view
withName:viewName
completionBlock:^(BOOL ready) {
OWSCAssert(ready);
DDLogInfo(@"%@ asyncRegisterExtension: %@ -> %d", self.tag, viewName, ready);
dispatch_async(dispatch_get_main_queue(), ^{
TSDatabaseView.sharedInstance.pendingViewRegistrations--;
});
}];
} else {
[[TSStorageManager sharedManager].database registerExtension:view withName:viewName];
}
}
+ (BOOL)registerUnreadDatabaseView
+ (void)registerUnreadDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -66,12 +139,13 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName
[self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
version:@"1"
async:NO];
}
+ (BOOL)registerUnseenDatabaseView
+ (void)asyncRegisterUnseenDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -84,12 +158,13 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName
[self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
version:@"1"
async:YES];
}
+ (BOOL)registerThreadSpecialMessagesDatabaseView
+ (void)asyncRegisterThreadSpecialMessagesDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -110,12 +185,13 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSThreadSpecialMessagesDatabaseViewExtensionName
[self registerMessageDatabaseViewWithName:TSThreadSpecialMessagesDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
version:@"1"
async:YES];
}
+ (BOOL)registerThreadInteractionsDatabaseView
+ (void)registerThreadInteractionsDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -125,12 +201,13 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return interaction.uniqueThreadId;
}];
return [self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName
[self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
version:@"1"
async:NO];
}
+ (BOOL)registerThreadOutgoingMessagesDatabaseView
+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -140,16 +217,18 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSThreadOutgoingMessageDatabaseViewExtensionName
[self registerMessageDatabaseViewWithName:TSThreadOutgoingMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"2"];
version:@"2"
async:YES];
}
+ (BOOL)registerThreadDatabaseView {
+ (void)registerThreadDatabaseView
{
YapDatabaseView *threadView =
[[TSStorageManager sharedManager].database registeredExtension:TSThreadDatabaseViewExtensionName];
if (threadView) {
return YES;
return;
}
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping
@ -178,8 +257,7 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
YapDatabaseView *databaseView =
[[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"1" options:options];
return [[TSStorageManager sharedManager]
.database registerExtension:databaseView
[[TSStorageManager sharedManager].database registerExtension:databaseView
withName:TSThreadDatabaseViewExtensionName];
}
@ -305,6 +383,40 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
}];
}
+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSUnseenDatabaseViewExtensionName];
if (!result) {
result = [transaction ext:TSUnreadDatabaseViewExtensionName];
OWSAssert(result);
}
return result;
}
+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSThreadOutgoingMessageDatabaseViewExtensionName];
OWSAssert(result);
return result;
}
+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSThreadSpecialMessagesDatabaseViewExtensionName];
OWSAssert(result);
return result;
}
#pragma mark - Logging
+ (NSString *)tag

@ -27,7 +27,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
+ (BOOL)isDatabasePasswordAccessible;
- (void)setupDatabase;
/**
* The safeBlockingMigrationsBlock block will
* run any outstanding version migrations that are a) blocking and b) safe
* to be run before the environment and storage is completely configured.
*
* Specifically, these migration should not depend on or affect the data
* of any database view.
*/
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)())safeBlockingMigrationsBlock;
- (void)deleteThreadsAndMessages;
- (void)resetSignalStorage;

@ -191,17 +191,30 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
};
}
- (void)setupDatabase
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)())safeBlockingMigrationsBlock
{
// Synchronously register extensions which are essential for views.
[TSDatabaseView registerThreadDatabaseView];
[TSDatabaseView registerThreadInteractionsDatabaseView];
[TSDatabaseView registerThreadOutgoingMessagesDatabaseView];
[TSDatabaseView registerUnreadDatabaseView];
[TSDatabaseView registerUnseenDatabaseView];
[TSDatabaseView registerThreadSpecialMessagesDatabaseView];
[self.database registerExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex] withName:@"idx"];
// Run the blocking migrations.
//
// These need to run _before_ the async registered database views or
// they will block on them, which (in the upgrade case) can block
// return of appDidFinishLaunching... which in term can cause the
// app to crash on launch.
safeBlockingMigrationsBlock();
// Asynchronously register other extensions.
//
// All sync registrations must be done before all async registrations,
// or the sync registrations will block on the async registrations.
[TSDatabaseView asyncRegisterUnseenDatabaseView];
[TSDatabaseView asyncRegisterThreadOutgoingMessagesDatabaseView];
[TSDatabaseView asyncRegisterThreadSpecialMessagesDatabaseView];
// Register extensions which aren't essential for rendering threads async
[[OWSIncomingMessageFinder new] asyncRegisterExtension];
[TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView];

Loading…
Cancel
Save