diff --git a/DcCore/DcCore/DC/events.swift b/DcCore/DcCore/DC/events.swift index 36f34520c..9242ab8cc 100644 --- a/DcCore/DcCore/DC/events.swift +++ b/DcCore/DcCore/DC/events.swift @@ -156,9 +156,6 @@ public class DcEventHandler { } case DC_EVENT_CONNECTIVITY_CHANGED: - if let sem = dcAccounts.fetchSemaphore, dcAccounts.isAllWorkDone() { - sem.signal() - } if accountId != dcAccounts.getSelected().id { return } @@ -167,6 +164,11 @@ public class DcEventHandler { NotificationCenter.default.post(name: eventConnectivityChanged, object: nil) } + case DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: + if let sem = dcAccounts.fetchSemaphore { + sem.signal() + } + case DC_EVENT_WEBXDC_STATUS_UPDATE: if accountId != dcAccounts.getSelected().id { return diff --git a/DcShare/Helper/ShareAttachment.swift b/DcShare/Helper/ShareAttachment.swift index a62dc13c7..170419a7b 100644 --- a/DcShare/Helper/ShareAttachment.swift +++ b/DcShare/Helper/ShareAttachment.swift @@ -138,7 +138,7 @@ class ShareAttachment { _ = self.addDcMsg(path: url.relativePath, viewType: DC_MSG_VIDEO) self.delegate?.onAttachmentChanged() if self.imageThumbnail == nil { - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url) DispatchQueue.main.async { self.delegate?.onThumbnailChanged() diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index 8ab658905..9fb2d8b83 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -132,7 +132,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // maybeNetwork() shall not be called in ui thread; // Reachability::reachabilityChanged uses DispatchQueue.main.async only logger.info("network: reachable \(reachability.connection.description)") - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in guard let self else { return } self.dcAccounts.maybeNetwork() if self.notifyToken == nil && @@ -145,7 +145,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD reachability.whenUnreachable = { _ in logger.info("network: not reachable") - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in self?.dcAccounts.maybeNetworkLost() } } @@ -233,7 +233,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD applicationInForeground = true dcAccounts.startIo() - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in guard let self else { return } if let reachability = self.reachability { if reachability.connection != .unavailable { @@ -484,35 +484,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD pushToDebugArray("1") // move work to non-main thread to not block UI (otherwise, in case we get suspended, the app is blocked totally) - // (we are using `qos: default` as `qos: .background` or `main.asyncAfter` may be delayed by tens of minutes) + // (we are using `qos: default` as `qos: .background` may be delayed by tens of minutes) DispatchQueue.global().async { [weak self] in guard let self else { completionHandler?(.failed); return } - // we're in background, run IO for a little time - self.dcAccounts.startIo() - self.dcAccounts.maybeNetwork() self.pushToDebugArray("2") self.addDebugFetchTimestamp() - - // create a new semaphore to make sure the received DC_CONNECTIVITY_CONNECTED really belongs to maybeNetwork() from above - // (maybeNetwork() sets connectivity to DC_CONNECTIVITY_CONNECTING, when fetch is done, we're back at DC_CONNECTIVITY_CONNECTED) self.dcAccounts.fetchSemaphore = DispatchSemaphore(value: 0) + let start = CFAbsoluteTimeGetCurrent() + + // backgroundFetch() pauses IO as needed + if !self.dcAccounts.backgroundFetch(timeout: 20) { + logger.error("backgroundFetch failed") + self.pushToDebugArray("ERR2") + } + let diff = CFAbsoluteTimeGetCurrent() - start + + // wait for DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE; + // without IO being started, more events that could interfere with shutdown are not added _ = self.dcAccounts.fetchSemaphore?.wait(timeout: .now() + 20) self.dcAccounts.fetchSemaphore = nil + let diff2 = CFAbsoluteTimeGetCurrent() - start - // TOCHECK: it seems, we are not always reaching this point in code, - // semaphore?.wait() does not always exit after the given timeout and the app gets suspended - - // maybe that is on purpose somehow to suspend inactive apps, not sure. - // this does not happen often, but still. - // cmp. https://github.com/deltachat/deltachat-ios/pull/1542#pullrequestreview-951620906 - logger.info("⬅️ finishing fetch") + logger.info("⬅️ finishing fetch in \(diff) s + \(diff2) s") self.pushToDebugArray(String(format: "3/%.3fs", Double(Date().timeIntervalSince1970)-nowTimestamp)) - if !self.appIsInForeground() { - self.dcAccounts.stopIo() - } - // 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) @@ -590,7 +587,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD return } eventHandlerActive = true - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in guard let self else { return } let eventHandler = DcEventHandler(dcAccounts: self.dcAccounts) let eventEmitter = self.dcAccounts.getEventEmitter() diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index 6506de4fa..805ead840 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -525,7 +525,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { // things that do not affect the chatview // and are delayed after the view is displayed - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in guard let self else { return } self.dcContext.marknoticedChat(chatId: self.chatId) } @@ -959,7 +959,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let indexPaths = tableView.indexPathsForVisibleRows { let visibleMessagesIds = indexPaths.map { UInt32(messageIds[$0.row]) } if !visibleMessagesIds.isEmpty { - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in self?.dcContext.markSeenMessages(messageIds: visibleMessagesIds) } } diff --git a/deltachat-ios/Controller/ConnectivityViewController.swift b/deltachat-ios/Controller/ConnectivityViewController.swift index 9d954dc42..606cf67c2 100644 --- a/deltachat-ios/Controller/ConnectivityViewController.swift +++ b/deltachat-ios/Controller/ConnectivityViewController.swift @@ -25,7 +25,7 @@ class ConnectivityViewController: WebViewViewController { self.loadHtml() } isLowDataMode = monitor.currentPath.isConstrained - monitor.start(queue: DispatchQueue.global(qos: .background)) + monitor.start(queue: DispatchQueue.global()) self.connectivityMonitor = monitor } } diff --git a/deltachat-ios/DC/DcAccount.swift b/deltachat-ios/DC/DcAccount.swift index de5ddfbc7..53b43dab2 100644 --- a/deltachat-ios/DC/DcAccount.swift +++ b/deltachat-ios/DC/DcAccount.swift @@ -59,10 +59,6 @@ public class DcAccounts { dc_accounts_maybe_network_lost(accountsPointer) } - public func isAllWorkDone() -> Bool { - return dc_accounts_all_work_done(accountsPointer) != 0 - } - public func startIo() { dc_accounts_start_io(accountsPointer) } @@ -71,6 +67,10 @@ public class DcAccounts { dc_accounts_stop_io(accountsPointer) } + public func backgroundFetch(timeout: UInt64) -> Bool { + return dc_accounts_background_fetch(accountsPointer, timeout) == 1 + } + public func select(id: Int) -> Bool { return dc_accounts_select_account(accountsPointer, UInt32(id)) == 1 } diff --git a/deltachat-ios/Extensions/UIImageView+Extensions.swift b/deltachat-ios/Extensions/UIImageView+Extensions.swift index c394f2cab..8ca98416f 100644 --- a/deltachat-ios/Extensions/UIImageView+Extensions.swift +++ b/deltachat-ios/Extensions/UIImageView+Extensions.swift @@ -6,7 +6,7 @@ extension UIImageView { func loadVideoThumbnail(from url: URL, placeholderImage: UIImage?, completionHandler: ((UIImage?) -> Void)?) { self.image = placeholderImage - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let thumbnailImage = DcUtils.generateThumbnailFromVideo(url: url) DispatchQueue.main.async { [weak self] in self?.image = thumbnailImage diff --git a/deltachat-ios/Handler/DeviceContactsHandler.swift b/deltachat-ios/Handler/DeviceContactsHandler.swift index d0bc70f13..38a448e40 100644 --- a/deltachat-ios/Handler/DeviceContactsHandler.swift +++ b/deltachat-ios/Handler/DeviceContactsHandler.swift @@ -42,7 +42,7 @@ class DeviceContactsHandler { private func fetchContactsWithEmailFromDevice(completionHandler: @escaping ([CNContact]) -> Void) { - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let keys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactEmailAddressesKey] var fetchedContacts: [CNContact] = [] diff --git a/deltachat-ios/Helper/FileHelper.swift b/deltachat-ios/Helper/FileHelper.swift index b0ae08eed..24d926297 100644 --- a/deltachat-ios/Helper/FileHelper.swift +++ b/deltachat-ios/Helper/FileHelper.swift @@ -63,7 +63,7 @@ public class FileHelper { public static func deleteFile(atPath: String?) { if Thread.isMainThread { - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { deleteFile(atPath) } } else { diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 50b7f2309..181921822 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -42,7 +42,7 @@ public class NotificationManager { } public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) { - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { NotificationManager.removePendingNotificationsFor(dcContext: dcContext, chatId: chatId) NotificationManager.removeDeliveredNotificationsFor(dcContext: dcContext, chatId: chatId) NotificationManager.updateApplicationIconBadge() @@ -70,7 +70,7 @@ public class NotificationManager { logger.info("notification background task will end soon") } - DispatchQueue.global(qos: .background).async { [weak self] in + DispatchQueue.global().async { [weak self] in guard let self else { return } if let ui = notification.userInfo, let chatId = ui["chat_id"] as? Int,