Fixed a few more threading-related bugs

Updated the libSession build script to reset and checkout submodules based on a flag (simplify the process)
Updated the share and notification extensions to generate the DSYM on debug builds to allow for better debugging
Added additional startup logging for debugging purposes
Tweaked the SNLog function to indicate when the log happens on the main thread (slightly less efficient but should help track down logic incorrectly running on the main thread)
Fixed a bug where we weren't recording the last PN server registration (so would re-register every time)
Fixed a bug where if the user sent the app to the background too quickly after launching the app wouldn't successfully startup when re-opening
Fixed a bug where the Notification and Share extensions would assert because they were dispatching the post-migration logic to the main thread (due to the threading changes in a previous release)
Fixed a bug where the Notification extension would incorrectly poll open groups on the main thread
This commit is contained in:
Morgan Pretty 2023-06-30 17:59:37 +10:00
parent d0be7f786c
commit ec81236615
15 changed files with 225 additions and 66 deletions

View File

@ -21,9 +21,13 @@
# path = "{FRAMEWORK NAME GOES HERE}";
# sourceTree = BUILD_DIR;
# };
#
# Note: We might one day be able to replace this with a local podspec if this GitHub feature
# request ever gets implemented: https://github.com/CocoaPods/CocoaPods/issues/8464
# Need to set the path or we won't find cmake
PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5
SHOULD_AUTO_INIT_SUBMODULES=${1:-false}
# Ensure the build directory exists (in case we need it before XCode creates it)
mkdir -p "${TARGET_BUILD_DIR}"
@ -41,11 +45,76 @@ if ! which cmake > /dev/null; then
exit 0
fi
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ]; then
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
echo "error: Need to fetch LibSession-Util submodule."
echo "error: Need to fetch LibSession-Util submodule." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
exit 1
# Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] & command -v git >/dev/null 2>&1; then
echo "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now"
git submodule foreach --recursive git reset --hard
git submodule update --init --recursive
echo "info: Checkout complete"
else
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)."
echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
exit 1
fi
else
are_submodules_valid() {
local PARENT_PATH=$1
# Change into the path to check for it's submodules
cd "${PARENT_PATH}"
local SUB_MODULE_PATHS=($(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'))
# If there are no submodules then return success based on whether the folder has any content
if [ ${#SUB_MODULE_PATHS[@]} -eq 0 ]; then
if [ "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
return 1
else
return 0
fi
fi
# Loop through the child submodules and check if they are valid
for i in "${!SUB_MODULE_PATHS[@]}"; do
local CHILD_PATH="${SUB_MODULE_PATHS[$i]}"
# If the child path doesn't exist then it's invalid
if [ ! -d "${CHILD_PATH}" ]; then
return 1
fi
are_submodules_valid "${PARENT_PATH}/${CHILD_PATH}"
local RESULT=$?
if [ "${RESULT}" -eq 1 ]; then
echo "info: Submodule ${CHILD_PATH} is in an invalid state."
return 1
fi
done
return 0
}
# Validate the state of the submodules
are_submodules_valid "${SRCROOT}/LibSession-Util"
HAS_INVALID_SUBMODULE=$?
if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then
echo "info: Submodules are in an invalid state, resetting and checking out recusively now"
cd "${SRCROOT}/LibSession-Util"
git submodule foreach --recursive git reset --hard
git submodule update --init --recursive
echo "info: Checkout complete"
else
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'."
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
exit 1
fi
fi
fi
# Generate a hash of the libSession-util source files and check if they differ from the last hash

View File

@ -6377,8 +6377,8 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 412;
DEBUG_INFORMATION_FORMAT = dwarf;
CURRENT_PROJECT_VERSION = 413;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -6449,7 +6449,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 412;
CURRENT_PROJECT_VERSION = 413;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6514,8 +6514,8 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 412;
DEBUG_INFORMATION_FORMAT = dwarf;
CURRENT_PROJECT_VERSION = 413;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -6588,7 +6588,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 412;
CURRENT_PROJECT_VERSION = 413;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -7496,7 +7496,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 412;
CURRENT_PROJECT_VERSION = 413;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -7567,7 +7567,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 412;
CURRENT_PROJECT_VERSION = 413;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

View File

@ -10,7 +10,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -10,7 +10,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -11,7 +11,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -11,7 +11,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -10,7 +10,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -10,7 +10,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot; true&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -17,6 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var window: UIWindow?
var backgroundSnapshotBlockerWindow: UIWindow?
var appStartupWindow: UIWindow?
var initialLaunchFailed: Bool = false
var hasInitialRootViewController: Bool = false
var startTime: CFTimeInterval = 0
private var loadingViewController: LoadingViewController?
@ -71,6 +72,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
migrationsCompletion: { [weak self] result, needsConfigSync in
if case .failure(let error) = result {
DispatchQueue.main.async {
self?.initialLaunchFailed = true
self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .databaseError(error))
}
return
@ -135,10 +137,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Resume database
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
// Reset the 'startTime' (since it would be invalid from the last launch)
startTime = CACurrentMediaTime()
// 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 {
let initialLaunchFailed: Bool = self.initialLaunchFailed
AppReadiness.invalidate()
// If the user went to the background too quickly then the database can be suspended before
// properly starting up, in this case an alert will be shown but we can recover from it so
// dismiss any alerts that were shown
if initialLaunchFailed {
self.window?.rootViewController?.dismiss(animated: false)
}
AppSetup.runPostSetupMigrations(
migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in
self?.loadingViewController?.updateProgress(
@ -149,18 +164,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
migrationsCompletion: { [weak self] result, needsConfigSync in
if case .failure(let error) = result {
DispatchQueue.main.async {
self?.showFailedStartupAlert(calledFrom: .enterForeground, error: .databaseError(error))
self?.showFailedStartupAlert(
calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed),
error: .databaseError(error)
)
}
return
}
self?.completePostMigrationSetup(calledFrom: .enterForeground, needsConfigSync: needsConfigSync)
self?.completePostMigrationSetup(
calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed),
needsConfigSync: needsConfigSync
)
}
)
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
if !hasInitialRootViewController { SNLog("Entered background before startup was completed") }
DDLog.flushLog()
// NOTE: Fix an edge case where user taps on the callkit notification
@ -264,6 +287,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
BackgroundPoller.isValid = true
AppReadiness.runNowOrWhenAppDidBecomeReady {
// If the 'AppReadiness' process takes too long then it's possible for the user to open
// the app after this closure is registered but before it's actually triggered - this can
// result in the `BackgroundPoller` incorrectly getting called in the foreground, this check
// is here to prevent that
guard CurrentAppContext().isInBackground() else { return }
BackgroundPoller.poll { result in
guard BackgroundPoller.isValid else { return }
@ -283,6 +312,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - App Readiness
private func completePostMigrationSetup(calledFrom lifecycleMethod: LifecycleMethod, needsConfigSync: Bool) {
SNLog("Migrations completed, performing setup and ensuring rootViewController")
Configuration.performMainSetup()
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
@ -292,6 +322,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// the user is in an invalid state (and should have already been shown a modal)
guard success else { return }
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
/// screen, if the PagedDatabaseObserver hasn't been setup yet then the home screen can show stale (ie. deleted)
@ -341,7 +374,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Add a log to track the proper startup time of the app so we know whether we need to
// improve it in the future from user logs
let endTime: CFTimeInterval = CACurrentMediaTime()
SNLog("Launch completed in \((self?.startTime).map { ceil((endTime - $0) * 1000) } ?? -1)ms")
SNLog("\(lifecycleMethod.timingName) completed in \((self?.startTime).map { ceil((endTime - $0) * 1000) } ?? -1)ms")
}
// May as well run these on the background thread
@ -429,6 +462,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
exit(0)
})
SNLog("Showing startup alert due to error: \(error.name)")
self.window?.rootViewController?.present(alert, animated: animated, completion: presentationCompletion)
}
@ -470,7 +504,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
private func handleActivation() {
guard Identity.userExists() else { return }
/// There is a _fun_ behaviour here where if the user launches the app, sends it to the background at the right time and then
/// opens it again the `AppReadiness` closures can be triggered before `applicationDidBecomeActive` has been
/// called again - this can result in odd behaviours so hold off on running this logic until it's properly called again
guard
Identity.userExists() &&
UserDefaults.sharedLokiProject?[.isMainAppActive] == true
else { return }
enableBackgroundRefreshIfNecessary()
JobRunner.appDidBecomeActive()
@ -492,7 +532,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Always call the completion block and indicate whether we successfully created the UI
guard
Storage.shared.isValid &&
(AppReadiness.isAppReady() || lifecycleMethod == .finishLaunching) &&
(
AppReadiness.isAppReady() ||
lifecycleMethod == .finishLaunching ||
lifecycleMethod == .enterForeground(initialLaunchFailed: true)
) &&
!hasInitialRootViewController
else { return DispatchQueue.main.async { onComplete(hasInitialRootViewController) } }
@ -854,10 +898,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - LifecycleMethod
private enum LifecycleMethod {
private enum LifecycleMethod: Equatable {
case finishLaunching
case enterForeground
case enterForeground(initialLaunchFailed: Bool)
case didBecomeActive
var timingName: String {
switch self {
case .finishLaunching: return "Launch"
case .enterForeground: return "EnterForeground"
case .didBecomeActive: return "BecomeActive"
}
}
static func == (lhs: LifecycleMethod, rhs: LifecycleMethod) -> Bool {
switch (lhs, rhs) {
case (.finishLaunching, .finishLaunching): return true
case (.enterForeground(let lhsFailed), .enterForeground(let rhsFailed)): return (lhsFailed == rhsFailed)
case (.didBecomeActive, .didBecomeActive): return true
default: return false
}
}
}
// MARK: - StartupError
@ -867,6 +928,15 @@ private enum StartupError: Error {
case failedToRestore
case startupTimeout
var name: String {
switch self {
case .databaseError(StorageError.startupFailed): return "Database startup failed"
case .failedToRestore: return "Failed to restore"
case .databaseError: return "Database error"
case .startupTimeout: return "Startup timeout"
}
}
var message: String {
switch self {
case .databaseError(StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized()

View File

@ -10,6 +10,8 @@ extern NSString *const ReportedApplicationStateDidChangeNotification;
@interface MainAppContext : NSObject <AppContext>
- (instancetype)init;
@end
NS_ASSUME_NONNULL_END

View File

@ -22,23 +22,25 @@ public enum SyncPushTokensJob: JobExecutor {
deferred: @escaping (Job) -> ()
) {
// Don't run when inactive or not in main app or if the user doesn't exist yet
guard
(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false),
Identity.userCompletedRequiredOnboarding()
else {
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
return deferred(job) // Don't need to do anything if it's not the main app
}
guard Identity.userCompletedRequiredOnboarding() else {
SNLog("[SyncPushTokensJob] Deferred due to incomplete registration")
deferred(job) // Don't need to do anything if it's not the main app
return
return deferred(job)
}
// We need to check a UIApplication setting which needs to run on the main thread so if we aren't on
// the main thread then swap to it
guard Thread.isMainThread else {
DispatchQueue.main.async {
run(job, queue: queue, success: success, failure: failure, deferred: deferred)
// We need to check a UIApplication setting which needs to run on the main thread so synchronously
// retrieve the value so we can continue
let isRegisteredForRemoteNotifications: Bool = {
guard !Thread.isMainThread else {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
return
}
return DispatchQueue.main.sync {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
}()
// Push tokens don't normally change while the app is launched, so you would assume checking once
// during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications"
@ -60,7 +62,7 @@ public enum SyncPushTokensJob: JobExecutor {
guard
job.behaviour == .runOnce ||
!UIApplication.shared.isRegisteredForRemoteNotifications ||
!isRegisteredForRemoteNotifications ||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
else {
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration")
@ -94,6 +96,7 @@ public enum SyncPushTokensJob: JobExecutor {
case .finished:
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
SNLog("[SyncPushTokensJob] Completed")
UserDefaults.standard[.lastPushNotificationSync] = Date()
Storage.shared.write { db in
db[.lastRecordedPushToken] = pushToken

View File

@ -12,7 +12,6 @@ import SignalCoreKit
public final class NotificationServiceExtension: UNNotificationServiceExtension {
private var didPerformSetup = false
private var areVersionMigrationsComplete = false
private var contentHandler: ((UNNotificationContent) -> Void)?
private var request: UNNotificationRequest?
@ -50,6 +49,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
defer {
Publishers
.MergeMany(openGroupPollingPublishers)
.subscribe(on: DispatchQueue.global(qos: .background))
.subscribe(on: DispatchQueue.main)
.sinkUntilComplete(
receiveCompletion: { _ in
self.completeSilenty()
@ -234,19 +235,23 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
$0 = NSENotificationPresenter()
}
},
migrationsCompletion: { [weak self] _, needsConfigSync in
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
migrationsCompletion: { [weak self] result, needsConfigSync in
switch result {
case .failure: SNLog("[NotificationServiceExtension] Failed to complete migrations")
case .success:
DispatchQueue.main.async {
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
}
}
completion()
}
)
}
@objc
private func versionMigrationsDidComplete(needsConfigSync: Bool) {
AssertIsOnMainThread()
areVersionMigrationsComplete = true
// If we need a config sync then trigger it now
if needsConfigSync {
Storage.shared.write { db in
@ -254,18 +259,17 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
}
checkIsAppReady()
checkIsAppReady(migrationsCompleted: true)
}
@objc
private func checkIsAppReady() {
private func checkIsAppReady(migrationsCompleted: Bool) {
AssertIsOnMainThread()
// Only mark the app as ready once.
guard !AppReadiness.isAppReady() else { return }
// App isn't ready until storage is ready AND all version migrations are complete.
guard Storage.shared.isValid && areVersionMigrationsComplete else { return }
guard Storage.shared.isValid && migrationsCompleted else { return }
SignalUtilitiesKit.Configuration.performMainSetup()

View File

@ -8,7 +8,6 @@ import SessionUIKit
import SignalCoreKit
final class ShareNavController: UINavigationController, ShareViewDelegate {
private var areVersionMigrationsComplete = false
public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>?
// MARK: - Error
@ -58,10 +57,16 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
$0 = NoopNotificationsManager()
}
},
migrationsCompletion: { [weak self] _, needsConfigSync in
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
migrationsCompletion: { [weak self] result, needsConfigSync in
switch result {
case .failure: SNLog("[SessionShareExtension] Failed to complete migrations")
case .success:
DispatchQueue.main.async {
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
}
}
}
)
@ -82,14 +87,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
ThemeManager.traitCollectionDidChange(previousTraitCollection)
}
@objc
func versionMigrationsDidComplete(needsConfigSync: Bool) {
AssertIsOnMainThread()
Logger.debug("")
areVersionMigrationsComplete = true
// If we need a config sync then trigger it now
if needsConfigSync {
Storage.shared.write { db in
@ -97,15 +99,14 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
}
}
checkIsAppReady()
checkIsAppReady(migrationsCompleted: true)
}
@objc
func checkIsAppReady() {
func checkIsAppReady(migrationsCompleted: Bool) {
AssertIsOnMainThread()
// App isn't ready until storage is ready AND all version migrations are complete.
guard areVersionMigrationsComplete else { return }
guard migrationsCompleted else { return }
guard Storage.shared.isValid else { return }
guard !AppReadiness.isAppReady() else {
// Only mark the app as ready once.
@ -195,6 +196,8 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
message: "vc_share_loading_message".localized()
) { activityIndicator in
publisher
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sinkUntilComplete(
receiveCompletion: { _ in activityIndicator.dismiss { } }
)

View File

@ -214,8 +214,14 @@ open class Storage {
self?.migrationProgressUpdater = nil
SUKLegacy.clearLegacyDatabaseInstance()
if case .failure(let error) = result {
SNLog("[Migration Error] Migration failed with error: \(error)")
// Don't log anything in the case of a 'success' or if the database is suspended (the
// latter will happen if the user happens to return to the background too quickly on
// launch so is unnecessarily alarming, it also gets caught and logged separately by
// the 'write' functions anyway)
switch result {
case .success: break
case .failure(DatabaseError.SQLITE_ABORT): break
case .failure(let error): SNLog("[Migration Error] Migration failed with error: \(error)")
}
onComplete(result, needsConfigSync)

View File

@ -4,10 +4,12 @@ import Foundation
import SignalCoreKit
public func SNLog(_ message: String) {
let threadString: String = (Thread.isMainThread ? " Main" : "")
#if DEBUG
print("[Session] \(message)")
print("[Session\(threadString)] \(message)")
#endif
OWSLogger.info("[Session] \(message)")
OWSLogger.info("[Session\(threadString)] \(message)")
}
public func SNLogNotTests(_ message: String) {