Skip to content

Commit

Permalink
add real shutdown for event handler and closing the db
Browse files Browse the repository at this point in the history
Does not crash as often anymore, but:
- [ ] delay after waking up / unsuspeding, link2xt said probably because of missing cache
- [ ] events are not received by the UI anymore, althrough I sucessfully restarted the handler that the events are printed to the console.
- [ ] there is a performance warning because I wait in the main thread for the event thread to finish: "Thread Performance Checker: Thread running at QOS_CLASS_USER_INTERACTIVE waiting on a lower QoS thread running at QOS_CLASS_BACKGROUND. Investigate ways to avoid priority inversions"

add more logging and add a startio statement

but this didn't fix the issue of the broken state on resume

add reloadDcContext, seems like this fixed the resume issue

do not close db if app is in foreground after fetch
  • Loading branch information
Simon-Laux authored and r10s committed Dec 1, 2023
1 parent 3a42f66 commit 02b7df1
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 26 deletions.
4 changes: 4 additions & 0 deletions DcCore/DcCore/DC/Wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public class DcAccounts {
dc_accounts_unref(accountsPointer)
accountsPointer = nil
}

public func isOpen() -> Bool {
return accountsPointer != nil
}

public func migrate(dbLocation: String) -> Int {
return Int(dc_accounts_migrate_account(accountsPointer, dbLocation))
Expand Down
1 change: 0 additions & 1 deletion DcShare/Helper/ShareAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ protocol ShareAttachmentDelegate: class {
}

class ShareAttachment {

weak var delegate: ShareAttachmentDelegate?
let dcContext: DcContext
let thumbnailSize = CGFloat(96)
Expand Down
97 changes: 72 additions & 25 deletions deltachat-ios/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,32 +67,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
continueDidFinishLaunchingWithOptions()
return true
}

// finishes the app initialization which depends on the successful access to the keychain
func continueDidFinishLaunchingWithOptions() {
if let sharedUserDefaults = UserDefaults.shared, !sharedUserDefaults.bool(forKey: UserDefaults.hasSavedKeyToKeychain) {
// we can assume a fresh install (UserDefaults are deleted on app removal)
// -> reset the keychain (which survives removals of the app) in case the app was removed and reinstalled.
if !KeychainManager.deleteDBSecrets() {
logger.warning("Failed to delete DB secrets")
}
}

do {
self.reachability = try Reachability()
} catch {
// TODO: Handle
}


func openAccountsWithKeychain() {
logger.info("🔐 openAccountsWithKeychain")
let accountIds = dcAccounts.getAll()
for accountId in accountIds {
let dcContext = dcAccounts.get(id: accountId)
if !dcContext.isOpen() {
do {
logger.info("🔐 start")
let secret = try KeychainManager.getAccountSecret(accountID: accountId)
logger.info("🔐 got secret")
if !dcContext.open(passphrase: secret) {
logger.error("Failed to open database for account \(accountId)")
}
logger.info("🔐 done")
} catch KeychainError.accessError(let message, let status) {
logger.error("Keychain error. \(message). Error status: \(status)")
return
Expand All @@ -103,7 +92,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
}
}
logger.info("🔐 openAccountsWithKeychain done")
}

// finishes the app initialization which depends on the successful access to the keychain
func continueDidFinishLaunchingWithOptions() {
if let sharedUserDefaults = UserDefaults.shared, !sharedUserDefaults.bool(forKey: UserDefaults.hasSavedKeyToKeychain) {
// we can assume a fresh install (UserDefaults are deleted on app removal)
// -> reset the keychain (which survives removals of the app) in case the app was removed and reinstalled.
if !KeychainManager.deleteDBSecrets() {
logger.warning("Failed to delete DB secrets")
}
}

do {
self.reachability = try Reachability()
} catch {
// TODO: Handle
}

openAccountsWithKeychain()

if dcAccounts.getAll().isEmpty, dcAccounts.add() == 0 {
fatalError("Could not initialize a new account.")
}
Expand Down Expand Up @@ -211,9 +220,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// applicationWillEnterForeground() is _not_ called on initial app start
func applicationWillEnterForeground(_: UIApplication) {
logger.info("➡️ applicationWillEnterForeground")
maybeStart()
applicationInForeground = true
dcAccounts.startIo()

DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
if let reachability = self.reachability {
Expand Down Expand Up @@ -257,6 +267,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func applicationDidBecomeActive(_: UIApplication) {
logger.info("➡️ applicationDidBecomeActive")
applicationInForeground = true
dcAccounts.startIo()
}

func applicationWillResignActive(_: UIApplication) {
Expand All @@ -271,7 +282,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD

func applicationWillTerminate(_: UIApplication) {
logger.info("⬅️ applicationWillTerminate")
dcAccounts.closeDatabase()
self.closeDB();
if let reachability = reachability {
reachability.stopNotifier()
}
Expand Down Expand Up @@ -309,8 +320,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
self.unregisterBackgroundTask()
} else if app.backgroundTimeRemaining < 10 {
logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), stopping")
self.dcAccounts.stopIo()

self.closeDB()
// to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
// we increase the probabilty that this happens by waiting a moment before calling unregisterBackgroundTask()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
Expand All @@ -323,6 +333,34 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
}
}

var shouldShutdownEventLoop: Bool = false
var eventShutdownSemaphore = DispatchSemaphore(value: 1)
func closeDB() {
// add flag to shutdown event handler
shouldShutdownEventLoop = true
// stopIo will generate atleast one event to the event handler can shut down
self.dcAccounts.stopIo()
// somehow wait for the eventhandler to be closed
eventShutdownSemaphore.wait()
// to avoid 0xdead10cc exceptions, we need to make sure there are no locks on files.
self.dcAccounts.closeDatabase()
shouldShutdownEventLoop = false
}

func maybeStart() {
logger.debug("⏫ maybe openDatabase again 🤔")
if !dcAccounts.isOpen() {
logger.debug("⏫ openDatabase again, because it is closed")
dcAccounts.openDatabase(writeable: true)
self.openAccountsWithKeychain()
logger.debug("⏫ openDatabase done")
if !eventHandlerActive {
installEventHandler()
}
reloadDcContext()
}
}


// MARK: - background fetch and notifications
Expand Down Expand Up @@ -465,6 +503,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
completionHandler(.newData)
return
}

maybeStart()

// from time to time, `didReceiveRemoteNotification` and `performFetchWithCompletionHandler`
// are actually called at the same millisecond.
Expand Down Expand Up @@ -527,11 +567,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD

if !self.appIsInForeground() {
self.dcAccounts.stopIo()
// to avoid 0xdead10cc exceptions, we need to make sure there are no locks on files.
self.closeDB()
}

// to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
// we increase the probabilty that this happens by waiting a moment before calling completionHandler()
usleep(1_000_000)
logger.info("⬅️ fetch done")
completionHandler(.newData)
if backgroundTask != .invalid {
Expand Down Expand Up @@ -597,16 +636,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
}

var eventHandlerActive = false
func installEventHandler() {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
let eventHandler = DcEventHandler(dcAccounts: self.dcAccounts)
let eventEmitter = self.dcAccounts.getEventEmitter()
eventHandlerActive = true
logger.info("⏫ event emitter started")
while true {
if shouldShutdownEventLoop {
break
}
guard let event = eventEmitter.getNextEvent() else { break }
eventHandler.handleEvent(event: event)
}
logger.info("⬅️ event emitter finished")
logger.info("⏬ event emitter finished")
eventShutdownSemaphore.signal()
eventHandlerActive = false
}
}

Expand Down

0 comments on commit 02b7df1

Please sign in to comment.