diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 70efe23dc..88dcaae4b 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1310,9 +1310,10 @@ extension ConversationVC: }, onCancel: { [weak self] modal in UIPasteboard.general.string = url.absoluteString - self?.showInputAccessoryView() - modal.dismiss(animated: true) + modal.dismiss(animated: true) { + self?.showInputAccessoryView() + } } ) ) diff --git a/Session/Meta/MainAppContext.swift b/Session/Meta/MainAppContext.swift index 4ae9c3fd4..84dc938ae 100644 --- a/Session/Meta/MainAppContext.swift +++ b/Session/Meta/MainAppContext.swift @@ -147,6 +147,12 @@ final class MainAppContext: AppContext { // stringlint:ignore_contents func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjects: [Any]) { + guard Thread.isMainThread else { + return DispatchQueue.main.async { [weak self] in + self?.ensureSleepBlocking(shouldBeBlocking, blockingObjects: blockingObjects) + } + } + if UIApplication.shared.isIdleTimerDisabled != shouldBeBlocking { if shouldBeBlocking { var logString: String = "Blocking sleep because of: \(String(describing: blockingObjects.first))" diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 39ea188ab..1d95ed80f 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -74075,7 +74075,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Your IP is visible to your call partner and an Oxen Foundation server while using beta calls." + "value" : "Your IP is visible to your call partner and a Session Technology Foundation server while using beta calls." } }, "eo" : { @@ -232561,6 +232561,61 @@ } } }, + "legacyGroupAfterDeprecationAdmin" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This group is now read-only. Recreate this group to keep chatting." + } + } + } + }, + "legacyGroupAfterDeprecationMember" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This group is now read-only. Ask the group admin to recreate this group to keep chatting." + } + } + } + }, + "legacyGroupBeforeDeprecationAdmin" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Groups have been upgraded! Recreate this group for improved reliability. This group will become read-only on {date}." + } + } + } + }, + "legacyGroupBeforeDeprecationMember" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Groups have been upgraded! Ask the group admin to recreate this group for improved reliability. This group will become read-only on {date}." + } + } + } + }, + "legacyGroupChatHistory" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat history will not be transferred to the new group. You can still view all chat history in your old group." + } + } + } + }, "legacyGroupMemberNew" : { "extractionState" : "manual", "localizations" : { @@ -354496,6 +354551,17 @@ } } }, + "recreateGroup" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recreate Group" + } + } + } + }, "redo" : { "extractionState" : "manual", "localizations" : { @@ -379053,6 +379119,17 @@ } } }, + "shareExtensionNoAccountError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oops! Looks like you don't have a {app_name} account yet.

You'll need to create one in the {app_name} app before you can share." + } + } + } + }, "shareToSession" : { "extractionState" : "manual", "localizations" : { diff --git a/Session/Onboarding/LandingScreen.swift b/Session/Onboarding/LandingScreen.swift index ea67bac3f..26a60d322 100644 --- a/Session/Onboarding/LandingScreen.swift +++ b/Session/Onboarding/LandingScreen.swift @@ -193,10 +193,11 @@ struct LandingScreen: View { UIApplication.shared.open(url) } }, - onCancel: { _ in + onCancel: { modal in if let url: URL = URL(string: "https://getsession.org/privacy-policy") { UIApplication.shared.open(url) } + modal.close() } ) ) diff --git a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift index 732765fba..99744d96e 100644 --- a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift @@ -361,9 +361,7 @@ public enum GarbageCollectionJob: JobExecutor { }, completion: { _ in // Dispatch async so we can swap from the write queue to a read one (we are done - // writing), this has to be done after a slight delay to ensure the transaction - // provided by the completion block completes first (ie. so we don't hit - // re-entrancy issues) + // writing) scheduler.schedule { // Retrieve a list of all valid attachmnet and avatar file paths let maybeFileInfo: FileInfo? = dependencies[singleton: .storage].read { db -> FileInfo in diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index a1e2a71c9..c18f94b7e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -777,7 +777,7 @@ public final class MessageSender { /// If we have affected rows then we should update them with the latest error text /// - /// **Note:** We `writeAsync` here as performing a syncronous `wrute` results in a reentrancy assertion + /// **Note:** We `writeAsync` here as performing a syncronous `write` results in a reentrancy assertion dependencies[singleton: .storage].writeAsync { db in let targetState: Interaction.State switch destination { diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index bd095a590..3cd1243c3 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -610,6 +610,9 @@ open class Storage { case StorageError.databaseSuspended: Log.error(.storage, "Database \(isWrite ? "write" : "read") failed as the database is suspended.") + case StorageError.transactionDeadlockTimeout: + Log.critical("[Storage] Database \(isWrite ? "write" : "read") failed due to a potential synchronous query deadlock timeout.") + default: break } } @@ -680,7 +683,7 @@ open class Storage { case (.invalid(let error), _): result = .failure(error) semaphore?.signal() - + case (.valid(let dbWriter), true): dbWriter.asyncWrite( { db in result = .success(try Storage.track(db, info, operation)) }, @@ -716,7 +719,7 @@ open class Storage { /// If this is a synchronous operation then `semaphore` will exist and will block here waiting on the signal from one of the /// above closures to be sent let semaphoreResult: DispatchTimeoutResult? = semaphore?.wait(timeout: .now() + .seconds(Storage.transactionDeadlockTimeoutSeconds)) - + /// If the transaction timed out then log the error and report a failure guard semaphoreResult != .timedOut else { StorageState.logIfNeeded(StorageError.transactionDeadlockTimeout, isWrite: info.isWrite) @@ -895,51 +898,55 @@ public extension Publisher where Failure == Error { // MARK: - CallInfo private extension Storage { - class CallInfo { - enum Behaviour { - case syncRead - case asyncRead - case syncWrite - case asyncWrite - } - - weak var storage: Storage? - let file: String - let function: String - let line: Int - let behaviour: Behaviour - - var callInfo: String { - let fileInfo: String = (file.components(separatedBy: "/").last.map { "\($0):\(line) - " } ?? "") + // MARK: - CallInfo + + private extension Storage { + class CallInfo { + enum Behaviour { + case syncRead + case asyncRead + case syncWrite + case asyncWrite + } - return "\(fileInfo)\(function)" - } - - var isWrite: Bool { - switch behaviour { - case .syncWrite, .asyncWrite: return true - case .syncRead, .asyncRead: return false + weak var storage: Storage? + let file: String + let function: String + let line: Int + let behaviour: Behaviour + + var callInfo: String { + let fileInfo: String = (file.components(separatedBy: "/").last.map { "\($0):\(line) - " } ?? "") + + return "\(fileInfo)\(function)" } - } - var isAsync: Bool { - switch behaviour { - case .asyncRead, .asyncWrite: return true - case .syncRead, .syncWrite: return false + + var isWrite: Bool { + switch behaviour { + case .syncWrite, .asyncWrite: return true + case .syncRead, .asyncRead: return false + } + } + var isAsync: Bool { + switch behaviour { + case .asyncRead, .asyncWrite: return true + case .syncRead, .syncWrite: return false + } + } + + init( + _ storage: Storage?, + _ file: String, + _ function: String, + _ line: Int, + _ behaviour: Behaviour + ) { + self.storage = storage + self.file = file + self.function = function + self.line = line + self.behaviour = behaviour } - } - - init( - _ storage: Storage?, - _ file: String, - _ function: String, - _ line: Int, - _ behaviour: Behaviour - ) { - self.storage = storage - self.file = file - self.function = function - self.line = line - self.behaviour = behaviour } } } @@ -967,7 +974,7 @@ private extension Storage { result?.timer = nil let action: String = (info.isWrite ? "write" : "read") - Log.warn(.storage, "Slow \(action) taking longer than \(Storage.slowTransactionThreshold, format: ".2", omitZeroDecimal: true)s - [ \(info.callInfo) ]") + Log.warn("[Storage] Slow \(action) taking longer than \(Storage.slowTransactionThreshold, format: ".2", omitZeroDecimal: true)s - [ \(info.callInfo) ]") result?.wasSlowTransaction = true } result.timer?.resume() @@ -983,7 +990,7 @@ private extension Storage { let end: CFTimeInterval = CACurrentMediaTime() let action: String = (info.isWrite ? "write" : "read") - Log.warn(.storage, "Slow \(action) completed after \(end - start, format: ".2", omitZeroDecimal: true)s - [ \(info.callInfo) ]") + Log.warn("[Storage] Slow \(action) completed after \(end - start, format: ".2", omitZeroDecimal: true)s - [ \(info.callInfo) ]") } } } diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 55dbd0033..13005027c 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -1355,7 +1355,7 @@ public final class JobQueue: Hashable { // thread and do so by creating a number of background queues to run the jobs on, if this // function was called on the wrong queue then we need to dispatch to the correct one guard DispatchQueue.with(key: queueKey, matches: queueContext, using: dependencies) else { - internalQueue.async { [weak self] in + internalQueue.async(using: dependencies) { [weak self] in self?.start(forceWhenAlreadyRunning: forceWhenAlreadyRunning) } return