diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 1f193601c..e2afa3890 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -143,6 +143,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // If we've already completed migrations at least once this launch then check // to see if any "delayed" migrations now need to run if Storage.shared.hasCompletedMigrations { + SNLog("Checking for pending migrations") let initialLaunchFailed: Bool = self.initialLaunchFailed AppReadiness.invalidate() @@ -154,30 +155,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.window?.rootViewController?.dismiss(animated: false) } - AppSetup.runPostSetupMigrations( - migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in - self?.loadingViewController?.updateProgress( - progress: progress, - minEstimatedTotalTime: minEstimatedTotalTime - ) - }, - migrationsCompletion: { [weak self] result, needsConfigSync in - if case .failure(let error) = result { - DispatchQueue.main.async { - self?.showFailedStartupAlert( - calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), - error: .databaseError(error) - ) + // Dispatch async so things can continue to be progressed if a migration does need to run + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + AppSetup.runPostSetupMigrations( + migrationProgressChanged: { progress, minEstimatedTotalTime in + self?.loadingViewController?.updateProgress( + progress: progress, + minEstimatedTotalTime: minEstimatedTotalTime + ) + }, + migrationsCompletion: { result, needsConfigSync in + if case .failure(let error) = result { + DispatchQueue.main.async { + self?.showFailedStartupAlert( + calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), + error: .databaseError(error) + ) + } + return } - return + + self?.completePostMigrationSetup( + calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), + needsConfigSync: needsConfigSync + ) } - - self?.completePostMigrationSetup( - calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), - needsConfigSync: needsConfigSync - ) - } - ) + ) + } } } @@ -322,8 +326,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // the user is in an invalid state (and should have already been shown a modal) guard success else { return } + SNLog("RootViewController ready, readying remaining processes") self?.initialLaunchFailed = false - SNLog("Migrations completed, performing setup and ensuring rootViewController") /// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some /// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index edc9618e0..1667f1384 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -206,11 +206,6 @@ open class Storage { } }) - // If we have an unperformed migration then trigger the progress updater immediately - if let firstMigrationKey: String = unperformedMigrations.first?.key { - self.migrationProgressUpdater?.wrappedValue(firstMigrationKey, 0) - } - // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in self?.migrationsCompleted.mutate { $0 = true } @@ -230,10 +225,17 @@ open class Storage { onComplete(result, needsConfigSync) } - // Update the 'migrationsCompleted' state (since we not support running migrations when - // returning from the background it's possible for this flag to transition back to false) - if unperformedMigrations.isEmpty { - self.migrationsCompleted.mutate { $0 = false } + // if there aren't any migrations to run then just complete immediately (this way the migrator + // doesn't try to execute on the DBWrite thread so returning from the background can't get blocked + // due to some weird endless process running) + guard !unperformedMigrations.isEmpty else { + migrationCompleted(.success(())) + return + } + + // If we have an unperformed migration then trigger the progress updater immediately + if let firstMigrationKey: String = unperformedMigrations.first?.key { + self.migrationProgressUpdater?.wrappedValue(firstMigrationKey, 0) } // Note: The non-async migration should only be used for unit tests @@ -377,16 +379,28 @@ open class Storage { updates: @escaping (Database) throws -> T ) -> (Database) throws -> T { return { db in + let start: CFTimeInterval = CACurrentMediaTime() + let fileName: String = (info.file.components(separatedBy: "/").last.map { " \($0):\(info.line)" } ?? "") let timeout: Timer = Timer.scheduledTimerOnMainThread(withTimeInterval: writeWarningThreadshold) { $0.invalidate() // Don't want to log on the main thread as to avoid confusion when debugging issues DispatchQueue.global(qos: .default).async { - let fileName: String = (info.file.components(separatedBy: "/").last.map { " \($0):\(info.line)" } ?? "") - SNLog("[Storage\(fileName)] Slow write taking longer than \(writeWarningThreadshold)s - \(info.function)") + SNLog("[Storage\(fileName)] Slow write taking longer than \(writeWarningThreadshold, format: ".2", omitZeroDecimal: true)s - \(info.function)") + } + } + defer { + // If we timed out then log the actual duration to help us prioritise performance issues + if !timeout.isValid { + let end: CFTimeInterval = CACurrentMediaTime() + + DispatchQueue.global(qos: .default).async { + SNLog("[Storage\(fileName)] Slow write completed after \(end - start, format: ".2", omitZeroDecimal: true)s") + } } + + timeout.invalidate() } - defer { timeout.invalidate() } return try updates(db) } diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index 8bb735ceb..42cd4c5a9 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -77,6 +77,23 @@ public extension String { // MARK: - Formatting +extension String.StringInterpolation { + mutating func appendInterpolation(_ value: Int, format: String) { + let result: String = String(format: "%\(format)d", value) + appendLiteral(result) + } + + mutating func appendInterpolation(_ value: Double, format: String, omitZeroDecimal: Bool = false) { + guard !omitZeroDecimal || Int(exactly: value) == nil else { + appendLiteral("\(Int(exactly: value)!)") + return + } + + let result: String = String(format: "%\(format)f", value) + appendLiteral(result) + } +} + public extension String { static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String { let secondsPerMinute: TimeInterval = 60