Rework database view registration.

This commit is contained in:
Matthew Chen 2017-12-18 22:56:02 -05:00
parent f88b954ab5
commit fe67cd924c
12 changed files with 156 additions and 147 deletions

View file

@ -135,7 +135,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
// Prevent the device from sleeping during database view async registration
// (e.g. long database upgrades).
//
// This block will be cleared in databaseViewRegistrationComplete.
// This block will be cleared in storageIsReady.
[DeviceSleepManager.sharedInstance addBlockWithBlockObject:self];
[AppSetup setupEnvironment:^{
@ -175,8 +175,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[OWSContactsSyncing sharedManager];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(databaseViewRegistrationComplete)
name:DatabaseViewRegistrationCompleteNotification
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationStateDidChange)
@ -713,9 +713,9 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
completionHandler:completionHandler];
}
- (void)databaseViewRegistrationComplete
- (void)storageIsReady
{
DDLogInfo(@"%@ databaseViewRegistrationComplete", self.logTag);
DDLogInfo(@"%@ storageIsReady", self.logTag);
[OWSPreferences setIsRegistered:[TSAccountManager isRegistered]];
@ -739,8 +739,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[Environment.current.contactsManager loadSignalAccountsFromCache];
[self ensureRootViewController];
// If there were any messages in our local queue which we hadn't yet processed.
[[OWSMessageReceiver sharedInstance] handleAnyUnprocessedEnvelopesAsync];
[[OWSBatchMessageProcessor sharedInstance] handleAnyUnprocessedEnvelopesAsync];
@ -766,6 +764,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
// Disable the SAE until the main app has successfully completed launch process
// at least once in the post-SAE world.
[OWSPreferences setIsReadyForAppExtensions];
[self ensureRootViewController];
}
- (void)registrationStateDidChange
@ -797,7 +797,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
{
DDLogInfo(@"%@ ensureRootViewController", self.logTag);
if ([TSDatabaseView hasPendingViewRegistrations] || self.hasInitialRootViewController) {
if (![OWSStorage isStorageReady] || self.hasInitialRootViewController) {
return;
}
self.hasInitialRootViewController = YES;

View file

@ -9,6 +9,7 @@
#import <AxolotlKit/SessionCipher.h>
#import <SignalMessaging/OWSProfileManager.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSStorage.h>
#import <SignalServiceKit/TextSecureKitEnv.h>
NS_ASSUME_NONNULL_BEGIN
@ -39,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
profileManager:OWSProfileManager.sharedManager];
[TextSecureKitEnv setSharedEnv:sharedEnv];
[[TSStorageManager sharedManager] setupDatabaseWithSafeBlockingMigrations:^{
[OWSStorage setupWithSafeBlockingMigrations:^{
[VersionMigrations runSafeBlockingMigrations];
}];
[[Environment current].contactsManager startObserving];

View file

@ -9,6 +9,7 @@
#import "OWSMessageManager.h"
#import "OWSQueues.h"
#import "OWSSignalServiceProtos.pb.h"
#import "OWSStorage.h"
#import "TSDatabaseView.h"
#import "TSStorageManager.h"
#import "TSYapDatabaseObject.h"
@ -261,8 +262,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
_isDrainingQueue = NO;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(databaseViewRegistrationComplete)
name:DatabaseViewRegistrationCompleteNotification
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
return self;
@ -273,7 +274,7 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)databaseViewRegistrationComplete
- (void)storageIsReady
{
[self drainQueue];
}
@ -306,9 +307,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
}
dispatch_async(self.serialQueue, ^{
if ([TSDatabaseView hasPendingViewRegistrations]) {
// We don't want to process incoming messages until database
// view registration is complete.
if (![OWSStorage isStorageReady]) {
// We don't want to process incoming messages until storage is ready.
return;
}

View file

@ -10,6 +10,7 @@
#import "OWSMessageDecrypter.h"
#import "OWSQueues.h"
#import "OWSSignalServiceProtos.pb.h"
#import "OWSStorage.h"
#import "TSDatabaseView.h"
#import "TSStorageManager.h"
#import "TSYapDatabaseObject.h"
@ -241,8 +242,8 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
_isDrainingQueue = NO;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(databaseViewRegistrationComplete)
name:DatabaseViewRegistrationCompleteNotification
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
return self;
@ -253,7 +254,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)databaseViewRegistrationComplete
- (void)storageIsReady
{
[self drainQueue];
}
@ -283,9 +284,8 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
}
dispatch_async(self.serialQueue, ^{
if ([TSDatabaseView hasPendingViewRegistrations]) {
// We don't want to process incoming messages until database
// view registration is complete.
if (![OWSStorage isStorageReady]) {
// We don't want to process incoming messages until storage is ready.
return;
}

View file

@ -9,6 +9,7 @@
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
#import "OWSReadReceiptsForSenderMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "OWSStorage.h"
#import "OWSSyncConfigurationMessage.h"
#import "TSContactThread.h"
#import "TSDatabaseView.h"
@ -176,8 +177,8 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
OWSSingletonAssert();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(databaseViewRegistrationComplete)
name:DatabaseViewRegistrationCompleteNotification
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
// Try to start processing.
@ -191,7 +192,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)databaseViewRegistrationComplete
- (void)storageIsReady
{
[self scheduleProcessing];
}
@ -202,9 +203,8 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)
{
if ([TSDatabaseView hasPendingViewRegistrations]) {
DDLogInfo(
@"%@ Deferring read receipt processing due to pending database view registrations.", self.logTag);
if (![OWSStorage isStorageReady]) {
DDLogInfo(@"%@ Deferring read receipt processing; storage not yet ready.", self.logTag);
return;
}
if (self.isProcessing) {

View file

@ -6,6 +6,8 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const StorageIsReadyNotification;
@class YapDatabaseExtension;
@interface OWSStorage : NSObject
@ -13,7 +15,23 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initStorage NS_DESIGNATED_INITIALIZER;
- (void)setSyncRegistrationsAreComplete;
+ (BOOL)isStorageReady;
- (void)runSyncRegistrations;
- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion;
- (BOOL)areAsyncRegistrationsComplete;
- (BOOL)areSyncRegistrationsComplete;
/**
* 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)setupWithSafeBlockingMigrations:(void (^_Nonnull)(void))safeBlockingMigrationsBlock;
+ (void)resetAllStorage;

View file

@ -5,6 +5,7 @@
#import "OWSStorage.h"
#import "AppContext.h"
#import "NSData+Base64.h"
#import "NSNotificationCenter+OWS.h"
#import "TSAttachmentStream.h"
#import "TSStorageManager.h"
#import <Curve25519Kit/Randomness.h>
@ -13,6 +14,8 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const StorageIsReadyNotification = @"StorageIsReadyNotification";
NSString *const OWSStorageExceptionName_DatabasePasswordInaccessibleWhileBackgrounded
= @"OWSStorageExceptionName_DatabasePasswordInaccessibleWhileBackgrounded";
NSString *const OWSStorageExceptionName_DatabasePasswordUnwritable
@ -26,7 +29,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
@protocol OWSDatabaseConnectionDelegate <NSObject>
- (BOOL)areSyncRegistrationsAreComplete;
- (BOOL)areSyncRegistrationsComplete;
@end
@ -80,7 +83,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.areSyncRegistrationsAreComplete);
OWSAssert(delegate.areSyncRegistrationsComplete);
[super readWriteWithBlock:block];
}
@ -89,7 +92,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.areSyncRegistrationsAreComplete);
OWSAssert(delegate.areSyncRegistrationsComplete);
[super asyncReadWriteWithBlock:block];
}
@ -99,7 +102,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.areSyncRegistrationsAreComplete);
OWSAssert(delegate.areSyncRegistrationsComplete);
[super asyncReadWriteWithBlock:block completionBlock:completionBlock];
}
@ -110,7 +113,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
{
id<OWSDatabaseConnectionDelegate> delegate = self.delegate;
OWSAssert(delegate);
OWSAssert(delegate.areSyncRegistrationsAreComplete);
OWSAssert(delegate.areSyncRegistrationsComplete);
[super asyncReadWriteWithBlock:block completionQueue:completionQueue completionBlock:completionBlock];
}
@ -232,7 +235,6 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
@interface OWSStorage () <OWSDatabaseConnectionDelegate>
@property (atomic, nullable) YapDatabase *database;
@property (atomic) BOOL areSyncRegistrationsAreComplete;
@end
@ -270,11 +272,82 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
return self;
}
- (void)setSyncRegistrationsAreComplete
- (BOOL)areAsyncRegistrationsComplete
{
OWSAssert(!self.areSyncRegistrationsAreComplete);
OWS_ABSTRACT_METHOD();
self.areSyncRegistrationsAreComplete = YES;
return NO;
}
- (BOOL)areSyncRegistrationsComplete
{
OWS_ABSTRACT_METHOD();
return NO;
}
- (void)runSyncRegistrations
{
OWS_ABSTRACT_METHOD();
}
- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion
{
OWS_ABSTRACT_METHOD();
}
+ (NSArray<OWSStorage *> *)allStorages
{
return @[
TSStorageManager.sharedManager,
];
}
+ (void)setupWithSafeBlockingMigrations:(void (^_Nonnull)(void))safeBlockingMigrationsBlock
{
OWSAssert(safeBlockingMigrationsBlock);
for (OWSStorage *storage in self.allStorages) {
[storage runSyncRegistrations];
}
// 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();
for (OWSStorage *storage in self.allStorages) {
[storage runAsyncRegistrationsWithCompletion:^{
[self postRegistrationCompleteNotificationIfPossible];
}];
}
}
+ (void)postRegistrationCompleteNotificationIfPossible
{
if (!self.isStorageReady) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:StorageIsReadyNotification
object:nil
userInfo:nil];
});
}
+ (BOOL)isStorageReady
{
for (OWSStorage *storage in self.allStorages) {
if (!storage.areAsyncRegistrationsComplete) {
return NO;
}
}
return YES;
}
- (BOOL)tryToLoadDatabase
@ -373,7 +446,9 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
+ (void)resetAllStorage
{
[[TSStorageManager sharedManager] resetStorage];
for (OWSStorage *storage in self.allStorages) {
[storage resetStorage];
}
[self deletePasswordFromKeychain];

View file

@ -4,8 +4,6 @@
#import <YapDatabase/YapDatabaseViewTransaction.h>
extern NSString *const DatabaseViewRegistrationCompleteNotification;
extern NSString *const TSInboxGroup;
extern NSString *const TSArchiveGroup;
extern NSString *const TSUnreadIncomingMessagesGroup;
@ -22,9 +20,6 @@ extern NSString *const TSSecondaryDevicesDatabaseViewExtensionName;
- (instancetype)init NS_UNAVAILABLE;
// This method can be called from any thread.
+ (BOOL)hasPendingViewRegistrations;
+ (void)registerCrossProcessNotifier;
// This method must be called _AFTER_ registerThreadInteractionsDatabaseView.
@ -57,7 +52,4 @@ extern NSString *const TSSecondaryDevicesDatabaseViewExtensionName;
// NOTE: It is not safe to call this method while hasPendingViewRegistrations is YES.
+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction;
// This method should be called _after_ all async database registrations have been started.
+ (void)asyncRegistrationCompletion;
@end

View file

@ -3,7 +3,6 @@
//
#import "TSDatabaseView.h"
#import "NSNotificationCenter+OWS.h"
#import "OWSDevice.h"
#import "OWSReadTracking.h"
#import "TSIncomingMessage.h"
@ -15,8 +14,6 @@
#import <YapDatabase/YapDatabaseCrossProcessNotification.h>
#import <YapDatabase/YapDatabaseViewTypes.h>
NSString *const DatabaseViewRegistrationCompleteNotification = @"DatabaseViewRegistrationCompleteNotification";
NSString *const TSInboxGroup = @"TSInboxGroup";
NSString *const TSArchiveGroup = @"TSArchiveGroup";
@ -33,62 +30,8 @@ NSString *const TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtens
NSString *const TSThreadSpecialMessagesDatabaseViewExtensionName = @"TSThreadSpecialMessagesDatabaseViewExtensionName";
NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName";
@interface TSDatabaseView ()
@property (nonatomic) BOOL areAllAsyncRegistrationsComplete;
@end
#pragma mark -
@implementation TSDatabaseView
+ (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;
}
- (BOOL)hasPendingViewRegistrations
{
@synchronized(self)
{
return !self.areAllAsyncRegistrationsComplete;
}
}
+ (BOOL)hasPendingViewRegistrations
{
return ![TSDatabaseView sharedInstance].areAllAsyncRegistrationsComplete;
}
- (void)setAreAllAsyncRegistrationsComplete
{
@synchronized(self)
{
OWSAssert(!self.areAllAsyncRegistrationsComplete);
self.areAllAsyncRegistrationsComplete = YES;
}
}
+ (void)registerCrossProcessNotifier
{
// I don't think the identifier and name of this extension matter for our purposes,
@ -444,19 +387,4 @@ NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevic
return result;
}
+ (void)asyncRegistrationCompletion
{
OWSAssertIsOnMainThread();
// All async registrations are complete when writes are unblocked.
[[TSStorageManager sharedManager].newDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[TSDatabaseView.sharedInstance setAreAllAsyncRegistrationsComplete];
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:DatabaseViewRegistrationCompleteNotification
object:nil
userInfo:nil];
}];
}
@end

View file

@ -12,16 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedManager;
/**
* 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)(void))safeBlockingMigrationsBlock;
- (YapDatabaseConnection *)dbReadConnection;
- (YapDatabaseConnection *)dbReadWriteConnection;
+ (YapDatabaseConnection *)dbReadConnection;

View file

@ -28,6 +28,9 @@ NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection;
@property (atomic) BOOL areAsyncRegistrationsComplete;
@property (atomic) BOOL areSyncRegistrationsComplete;
@end
#pragma mark -
@ -69,7 +72,7 @@ NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
[super resetStorage];
}
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)(void))safeBlockingMigrationsBlock
- (void)runSyncRegistrations
{
// Synchronously register extensions which are essential for views.
[TSDatabaseView registerCrossProcessNotifier];
@ -86,15 +89,13 @@ NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
// seeing, this issue only seems to affect sync and not async registrations. We've always
// been opening write transactions before the async registrations complete without negative
// consequences.
[self setSyncRegistrationsAreComplete];
OWSAssert(!self.areSyncRegistrationsComplete);
self.areSyncRegistrationsComplete = YES;
}
// 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();
- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion
{
OWSAssert(completion);
// Asynchronously register other extensions.
//
@ -111,10 +112,14 @@ NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
[OWSFailedMessagesJob asyncRegisterDatabaseExtensionsWithStorageManager:self];
[OWSFailedAttachmentDownloadsJob asyncRegisterDatabaseExtensionsWithStorageManager:self];
// NOTE: [TSDatabaseView asyncRegistrationCompletion] ensures that
// DatabaseViewRegistrationCompleteNotification is not fired until all
// of the async registrations are complete.
[TSDatabaseView asyncRegistrationCompletion];
// Block until all async registrations are complete.
[self.newDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
OWSAssert(!self.areAsyncRegistrationsComplete);
self.areAsyncRegistrationsComplete = YES;
completion();
}];
}
+ (void)protectFiles
@ -216,12 +221,12 @@ NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory
+ (YapDatabaseConnection *)dbReadConnection
{
return TSStorageManager.dbReadConnection;
return TSStorageManager.sharedManager.dbReadConnection;
}
+ (YapDatabaseConnection *)dbReadWriteConnection
{
return TSStorageManager.dbReadWriteConnection;
return TSStorageManager.sharedManager.dbReadWriteConnection;
}
- (void)deleteDatabaseFile

View file

@ -102,8 +102,8 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE
OWSContactsSyncing.sharedManager()
NotificationCenter.default.addObserver(self,
selector: #selector(databaseViewRegistrationComplete),
name: .DatabaseViewRegistrationComplete,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(registrationStateDidChange),
@ -177,7 +177,7 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE
}
@objc
func databaseViewRegistrationComplete() {
func storageIsReady() {
AssertIsOnMainThread()
Logger.debug("\(self.logTag) \(#function)")
@ -232,7 +232,7 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE
private func ensureRootViewController() {
Logger.debug("\(self.logTag) \(#function)")
guard !TSDatabaseView.hasPendingViewRegistrations() else {
guard OWSStorage.isStorageReady() else {
return
}
guard !hasInitialRootViewController else {