Skip to content

Commit 396a4b6

Browse files
authored
Don't allow list media integrity check to race with backup uploads
1 parent b6a9d23 commit 396a4b6

File tree

2 files changed

+71
-29
lines changed

2 files changed

+71
-29
lines changed

Signal/src/ViewControllers/AppSettings/Internal/InternalListMediaViewController.swift

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,22 @@ class InternalListMediaViewController: OWSTableViewController2 {
1414
super.viewDidLoad()
1515

1616
title = "Backup media debug"
17-
18-
updateTableContents()
1917
}
2018

21-
private var lastResult: ListMediaIntegrityCheckResult?
19+
override func viewDidAppear(_ animated: Bool) {
20+
super.viewDidAppear(animated)
21+
22+
ModalActivityIndicatorViewController.present(
23+
fromViewController: self,
24+
asyncBlock: { [weak self] modal in
25+
try? await DependenciesBridge.shared.backupListMediaManager.queryListMediaIfNeeded()
26+
await MainActor.run {
27+
self?.updateTableContents()
28+
}
29+
modal.dismiss(animated: true)
30+
}
31+
)
32+
}
2233

2334
func updateTableContents() {
2435
let contents = OWSTableContents()
@@ -28,6 +39,7 @@ class InternalListMediaViewController: OWSTableViewController2 {
2839
pendingUploadThumbnailCount,
2940
pendingOrphanDeleteCount,
3041
lastListMediaFailure,
42+
lastListMediaResult,
3143
) = DependenciesBridge.shared.db.read { tx in
3244
return (
3345
try! QueuedBackupAttachmentUpload
@@ -37,7 +49,8 @@ class InternalListMediaViewController: OWSTableViewController2 {
3749
.filter(Column(QueuedBackupAttachmentUpload.CodingKeys.isFullsize) == false)
3850
.fetchCount(tx.database),
3951
try! OrphanedBackupAttachment.fetchCount(tx.database),
40-
try! DependenciesBridge.shared.backupListMediaManager.getLastFailingIntegrityCheckResult(tx: tx)
52+
try! DependenciesBridge.shared.backupListMediaManager.getLastFailingIntegrityCheckResult(tx: tx),
53+
try! DependenciesBridge.shared.backupListMediaManager.getMostRecentIntegrityCheckResult(tx: tx)
4154
)
4255
}
4356

@@ -54,24 +67,23 @@ class InternalListMediaViewController: OWSTableViewController2 {
5467
contents.add(lastFailureSection)
5568

5669
let lastResultSection = OWSTableSection(title: "Latest result")
57-
if let lastResult {
58-
Self.populate(section: lastResultSection, with: lastResult)
70+
if let lastListMediaResult {
71+
Self.populate(section: lastResultSection, with: lastListMediaResult)
5972
}
6073
lastResultSection.add(.actionItem(withText: "Perform remote integrity check", actionBlock: { [weak self] in
6174
guard let self else { return }
62-
ModalActivityIndicatorViewController.present(
63-
fromViewController: self,
64-
asyncBlock: { [weak self] modal in
65-
guard let result = try? await DependenciesBridge.shared.backupListMediaManager.performUploadIntegrityCheck() else {
66-
return
67-
}
68-
await MainActor.run {
69-
self?.lastResult = result
70-
self?.updateTableContents()
71-
}
72-
modal.dismiss(animated: true)
73-
}
75+
let vc = ActionSheetController(
76+
title: "This will schedule the integrity check to run on next app launch, then exit the app. "
77+
+ "After tapping \"Okay\", please relaunch the app and return to this screen to check the results."
7478
)
79+
vc.addAction(.init(title: "Okay", handler: { _ in
80+
DependenciesBridge.shared.db.write { tx in
81+
DependenciesBridge.shared.backupListMediaManager.setManualNeedsListMedia(tx: tx)
82+
}
83+
exit(0)
84+
}))
85+
vc.addAction(.cancel)
86+
present(vc, animated: true)
7587
}))
7688
contents.add(lastResultSection)
7789

SignalServiceKit/Backups/Attachments/BackupListMediaManager.swift

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ public protocol BackupListMediaManager {
7979

8080
func getLastFailingIntegrityCheckResult(tx: DBReadTransaction) throws -> ListMediaIntegrityCheckResult?
8181

82-
func performUploadIntegrityCheck() async throws -> ListMediaIntegrityCheckResult
82+
func getMostRecentIntegrityCheckResult(tx: DBReadTransaction) throws -> ListMediaIntegrityCheckResult?
83+
84+
func setManualNeedsListMedia(tx: DBWriteTransaction)
8385
}
8486

8587
public class BackupListMediaManagerImpl: BackupListMediaManager {
@@ -151,8 +153,17 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
151153
try kvStore.getCodableValue(forKey: Constants.lastNonEmptyIntegrityCheckResultKey, transaction: tx)
152154
}
153155

156+
public func getMostRecentIntegrityCheckResult(tx: DBReadTransaction) throws -> ListMediaIntegrityCheckResult? {
157+
try kvStore.getCodableValue(forKey: Constants.lastIntegrityCheckResultKey, transaction: tx)
158+
}
159+
154160
private let taskQueue = ConcurrentTaskQueue(concurrentLimit: 1)
155161

162+
/// Nil if we have not run list media this app launch (only held in memory).
163+
/// Set to the upload era where we ran list media this app launch.
164+
/// Should only be accessed from the taskQueue for locking purposes.
165+
private var lastListMediaUploadEraThisAppSession: String?
166+
156167
public func queryListMediaIfNeeded() async throws {
157168
let task = Task {
158169
// Enqueue in a concurrent(1) task queue; we only want to run one of these at a time.
@@ -175,13 +186,7 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
175186
)
176187
}
177188

178-
public func performUploadIntegrityCheck() async throws -> ListMediaIntegrityCheckResult {
179-
return try await taskQueue.run {
180-
try await self._queryListMediaIfNeeded(forceIfNotNeeded: true)
181-
}
182-
}
183-
184-
private func _queryListMediaIfNeeded(forceIfNotNeeded: Bool = false) async throws -> ListMediaIntegrityCheckResult {
189+
private func _queryListMediaIfNeeded() async throws -> ListMediaIntegrityCheckResult {
185190
guard FeatureFlags.Backups.supported else {
186191
return .empty(listMediaStartTimestamp: 0)
187192
}
@@ -216,7 +221,7 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
216221
integrityCheckResult
217222
)
218223
}
219-
guard needsToQuery || forceIfNotNeeded else {
224+
guard needsToQuery else {
220225
return .empty(listMediaStartTimestamp: 0)
221226
}
222227

@@ -1115,6 +1120,16 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
11151120
case .free:
11161121
return false
11171122
case .paid, .paidExpiringSoon, .paidAsTester:
1123+
// Only query once per app session per upload era; this overrides the manual
1124+
// toggle and the date-based checks.
1125+
if lastListMediaUploadEraThisAppSession == currentUploadEra {
1126+
return false
1127+
}
1128+
1129+
if kvStore.getBool(Constants.manuallySetNeedsListMediaKey, defaultValue: false, transaction: tx) {
1130+
return true
1131+
}
1132+
11181133
// If paid tier, query periodically as a catch-all to ensure local state
11191134
// stays in sync with the server.
11201135
let nowMs = dateProvider().ows_millisecondsSince1970
@@ -1157,16 +1172,19 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
11571172
self.kvStore.setBool(true, key: Constants.hasEverRunListMediaKey, transaction: tx)
11581173
if let uploadEra = kvStore.getString(Constants.inProgressUploadEraKey, transaction: tx) {
11591174
self.kvStore.setString(uploadEra, key: Constants.lastListMediaUploadEraKey, transaction: tx)
1175+
self.lastListMediaUploadEraThisAppSession = uploadEra
11601176
self.kvStore.removeValue(forKey: Constants.inProgressUploadEraKey, transaction: tx)
11611177
} else {
11621178
owsFailDebug("Missing in progress upload era?")
11631179
}
11641180
self.kvStore.setUInt64(startTimestamp, key: Constants.lastListMediaStartTimestampKey, transaction: tx)
1181+
self.kvStore.setBool(false, key: Constants.manuallySetNeedsListMediaKey, transaction: tx)
11651182
kvStore.removeValue(forKey: Constants.inProgressListMediaStartTimestampKey, transaction: tx)
11661183

11671184
if integrityCheckResult.hasFailures {
11681185
try kvStore.setCodable(integrityCheckResult, key: Constants.lastNonEmptyIntegrityCheckResultKey, transaction: tx)
11691186
}
1187+
try kvStore.setCodable(integrityCheckResult, key: Constants.lastIntegrityCheckResultKey, transaction: tx)
11701188
kvStore.removeValue(forKey: Constants.inProgressIntegrityCheckResultKey, transaction: tx)
11711189

11721190
self.kvStore.setBool(false, key: Constants.hasCompletedListingMediaKey, transaction: tx)
@@ -1184,10 +1202,15 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
11841202
// Rotate the last integrity check failure when disabled
11851203
db.write { tx in
11861204
kvStore.removeValue(forKey: Constants.lastNonEmptyIntegrityCheckResultKey, transaction: tx)
1205+
kvStore.removeValue(forKey: Constants.lastIntegrityCheckResultKey, transaction: tx)
11871206
}
11881207
}
11891208
}
11901209

1210+
public func setManualNeedsListMedia(tx: DBWriteTransaction) {
1211+
kvStore.setBool(true, key: Constants.manuallySetNeedsListMediaKey, transaction: tx)
1212+
}
1213+
11911214
private enum Constants {
11921215
/// Maps to the upload era (active subscription) when we last queried the list media
11931216
/// endpoint, or nil if its never been queried.
@@ -1197,6 +1220,8 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
11971220
static let lastListMediaStartTimestampKey = "lastListMediaTimestamp"
11981221
static let inProgressListMediaStartTimestampKey = "inProgressListMediaTimestamp"
11991222

1223+
static let manuallySetNeedsListMediaKey = "manuallySetNeedsListMediaKey"
1224+
12001225
/// True if we've ever run list media in the lifetime of this app.
12011226
static let hasEverRunListMediaKey = "hasEverRunListMedia"
12021227

@@ -1221,6 +1246,7 @@ public class BackupListMediaManagerImpl: BackupListMediaManager {
12211246
static let hasCompletedEnumeratingAttachmentsKey = "hasCompletedEnumeratingAttachmentsKey"
12221247

12231248
static let lastNonEmptyIntegrityCheckResultKey = "lastNonEmptyIntegrityCheckResultKey"
1249+
static let lastIntegrityCheckResultKey = "lastIntegrityCheckResultKey"
12241250
static let inProgressIntegrityCheckResultKey = "inProgressIntegrityCheckResultKey"
12251251
}
12261252
}
@@ -1238,8 +1264,12 @@ class MockBackupListMediaManager: BackupListMediaManager {
12381264
nil
12391265
}
12401266

1241-
func performUploadIntegrityCheck() async throws -> ListMediaIntegrityCheckResult {
1242-
return .empty(listMediaStartTimestamp: 0)
1267+
func getMostRecentIntegrityCheckResult(tx: DBReadTransaction) throws -> ListMediaIntegrityCheckResult? {
1268+
nil
1269+
}
1270+
1271+
func setManualNeedsListMedia(tx: DBWriteTransaction) {
1272+
// Nothing
12431273
}
12441274
}
12451275

0 commit comments

Comments
 (0)