Repository navigation
Conversation
|
|
Manual verification completed on the iOS Simulator using temporarily shortened failure intervals:
The production failure intervals were restored after verification. |
jvsena42
left a comment
There was a problem hiding this comment.
The immediate check on foreground adds a new stale-toast path at cold launch, the same class this PR removes. Details inline; the second comment is a smaller, optional point.
Checked and clean:
- The gate does not drop notifications permanently:
shouldNotifyreturns before consuming the cooldown when inactive, and everysetAppActive(true)re-runs the check. A Task that lands after backgrounding is suppressed without consuming the cooldown. - The recovered/running status is re-read before emitting, under
statusUpdateQueue, matching the writes inupdateBackupStatus. - No deadlock:
stateQueueandstatusUpdateQueuenever sync back onto the backup queue. - A persistent failure while active still toasts at +30 min and then every 10 min, as on master.
- The wall-clock guards replace a
UInt64underflow trap on master with a skip, which is the right outcome when the clock is behindrequired. - Wipe clears
backupStatuses, so the launch check during onboarding finds nothing.
No Android twin: BackupRepo.checkForFailedBackups has neither the scene gate nor the in-progress exemption.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA reviewed on 27367a5.
QA review
No new actionable code findings.
QA review
Reviewed the full PR diff against its merge base 521ce0f, at 27367a5. Full review of the current diff, with no inherited baseline.
No new actionable code findings.
The pass covered scene-phase gating in AppScene, the failure gate, the status recheck on statusUpdateQueue, the in-progress exclusion, cooldown handling, and BackupFailureNotificationGateTests, plus the launch order of startObservingBackups and scheduleFullBackup.
Earlier threads were checked on this revision. The foreground grace timer is gone, so a short active session no longer postpones an overdue alert (short sessions). The queued notification re-reads backup status before it consumes the cooldown (recovered backups). The immediate foreground check runs only when backup observation has already started and the scene is still active, so a cold or offline launch does not toast from the first .active callback (cold launch). A running upload still withholds this toast until that attempt finishes; that limitation stays in the existing thread.
Android parity was not part of this review.
Validation: Source was inspected at 27367a5. The new gate tests were read and were not executed in this review. Run Tests and Run Integration Tests succeeded on this commit. The local e2e job was still queued and is not evidence for this gate. The author reports a simulator pass with shortened failure intervals (comment); that report is not a test performed in this review. The PR's two manual cases still apply: recovery while locked, and an active overdue failure that honors the ten-minute cooldown. No new Appium spec is indicated; the path depends on a 30-minute failure age and network-loss injection, and this PR does not change journeys or accessibility identifiers.
Additional test cases
- Persisted overdue failure (required, not running, older than 30 minutes). Relaunch online. The splash and PIN screen stay free of a backup-failure toast while
scheduleFullBackupretries. If that retry fails and the backup is still required and not running after observation has started, the toast comes from the periodic check. - Same persisted failure with the device offline. Launch Bitkit. The backup-failure toast stays absent while the node never starts.
- Scene active, one backup
runningwithrequiredolder than 30 minutes. The failure toast stays absent until that attempt ends in failure. A success ends with no toast. - Observation already started, backup overdue and not running. Foreground for under a minute, background, and repeat. One toast appears on an active scene. Backgrounding leaves the ten-minute cooldown in place, so a second toast waits until that cooldown has elapsed.
Device testing: not performed in this review.
Ready for device testing.
jvsena42
left a comment
There was a problem hiding this comment.
27367a5: fixed. The foreground check now runs only once isObserving is set, which happens on node .running, before scheduleFullBackup(). A cold launch therefore leaves the first check to the periodic loop, after the retry. Later foregrounds still get the immediate recheck, and the gate re-checks isActive after the hop to the backup queue. No new findings.
27367a5 to
70b6ae5
Compare
jvsena42
left a comment
There was a problem hiding this comment.
No finding at 70b6ae5; the tree is the one I read at 27367a5.
Checked: the gate never consumes the cooldown when it suppresses (inactive scene, throttled, or the re-read under statusUpdateQueue finding no failure); a failure that is still overdue is re-checked on every .active and by the periodic loop; the cold-launch .active is skipped until isObserving; the >= guards replace the UInt64 underflow on master; wipe and restore reset the statuses the check reads.
build-local is red at this head because the run was cancelled during package resolution (run 36606127919, conclusion cancelled), and e2e-status failed for lack of its artifact. Run Tests and the integration tests passed. The e2e workflow needs a re-run before merge.
piotr-iohk
left a comment
There was a problem hiding this comment.
@pwltr please resolve the merge conflicts. QA review has not been performed for this request.
70b6ae5 to
c58ffc3
Compare
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Full reassessment of the entire PR diff against merge base b45b22a, at c58ffc3. The saved assessment had no usable coverage baseline, so nothing was inherited.
No new actionable code findings.
The scene-activity gate, the observation guard on the foreground check, the status re-read on statusUpdateQueue before the cooldown is consumed, the in-progress exclusion, and the ten-minute cooldown match the behavior described for #562. A cold or offline launch does not emit from the first .active callback, because that check waits until backup observation has started. Short returns to the foreground do not reset the cooldown. Earlier comments on short sessions, recovered backups, and cold launch do not describe this revision. A backup left running by a stalled upload still withholds this toast; that limitation remains the accepted note in the existing thread.
Android BackupRepo.checkForFailedBackups at 31b05e7 still emits from the periodic loop with no foreground or in-progress gate. That difference is the iOS lock/unlock fix, not a defect in this PR.
Validation: Source was inspected at c58ffc3. BackupFailureNotificationGateTests was read and was not executed in this review. Run Tests and Run Integration Tests succeeded on this commit. The local e2e job was still queued and is not evidence for this gate. The author reports a simulator pass with shortened failure intervals (comment); that report is not a test performed in this review.
Device testing: not performed in this review.
Ready for device testing.
Suggested additional test cases
- iOS. Persisted overdue failure (required, not running, older than 30 minutes). Relaunch online. The splash and PIN screen stay free of a backup-failure toast while
scheduleFullBackupretries. If that retry fails and the backup is still required and not running, the toast appears after that attempt finishes. - iOS. Same persisted failure, device offline. Launch Bitkit. The backup-failure toast stays absent while the node never starts.
- iOS. Scene active, one backup
runningwithrequiredolder than 30 minutes. The failure toast stays absent until that attempt ends. A success ends with no toast. A failure ends with the toast.
jvsena42
left a comment
There was a problem hiding this comment.
Code scan at c58ffc3: no issues found. The rebase replays the same patches against current master (only context and offsets changed), so I checked how they behave against the new master code.
Checked and clean:
- Cold-launch fix still holds: the foreground check waits for
isObserving(BackupService.swift:192-211), the scene-phase observer keepsinitial: true(AppScene.swift:535), and nothing returns early beforesetAppActive(:1254). - A real failure is never silenced. Every new master path that sets
running: true(scheduleBackup,markBackupRequired(schedule:), the start ofrunBackup) ends withrunning: falseon success, failure, cancellation or loop exit. A.failedoutcome stops the loop and leaves the backup required, and both the periodic check and the failure catch report it. - Paykit wait:
PaykitPaymentActivity.waitUntilIdle()keepsrunningtrue, but that means the attempt hasn't run yet, not that it failed. - Deadlock: none in the emit-time re-read under
statusUpdateQueue. - Reset paths: wipe clears the statuses, restore marks them synced, init clears
running, andstopObservingBackupsclearsisObserving. - The hung JWT refresh stays the accepted limitation from the earlier thread.
Perf: not relevant. l10n: no UI text changed.
Device gate (partial): c58ffc3 — not run yet. Pending: the two Manual Tests (a backup held failing past the 30-minute window, then foreground and cold launch). Device results will follow in a separate review.
|
Device gate (partial): To switch the failure on and off live, I used a scratch-only fault hook that is not PR code: The app stayed active on Home throughout:
So a real, ongoing failure is still reported while the app is active. Seen on master's code too, not from this PR: only BLOCKTANK retries on a timer. A failed ACTIVITY, METADATA or SETTINGS backup stays failed until retried from Settings → Data backups. Still running: Manual Test 1 (locked for more than 30 minutes, connectivity restored while locked, then unlock and cold launch). |
|
Device gate (partial 2):
Reading of the code: Still running: the same sequence on master with the same hook, to tell whether this changed. The finding and its severity will follow once that baseline lands. |
jvsena42
left a comment
There was a problem hiding this comment.
One MEDIUM from the device inline: Manual Test 1 (unlock after connectivity returns) still shows the stale backup-failure toast, the same as master.
Device gate: c58ffc3 — manual tests: 1 passed, 1 failed (Manual Test 1, unlock step; the cold-launch part passes); perf: not relevant; Figma: n/a; l10n: no UI text changed.
- Manual Test 2 passes. A backup failing for more than 30 minutes while active still shows the toast (12:21:51), and the repeats keep the 10-minute cooldown (12:31:51, 12:41:53).
- The code scan from earlier today stands: no real failure is silenced, and the reset paths are clean.
- Seen on master's code too, not from this PR: only BLOCKTANK retries on a timer, so a failed ACTIVITY, METADATA or SETTINGS backup waits for a manual retry or a cold launch.
| } | ||
|
|
||
| guard shouldCheck else { return } | ||
| self.checkForFailedBackups() |
There was a problem hiding this comment.
Manual Test 1 still shows the stale failure toast on unlock, the same as master. On foreground this runs checkForFailedBackups() straight away. A category whose upload failed before the lock is still isRequired && !running, with required over 30 minutes old. Nothing retries it before the check, so connectivity restored while locked still produces "Data Backup Failure … Retrying in 1 minute." 1 ms after the scene turns active.
The failed category is retried only on the next cold launch. BLOCKTANK is the only category with a periodic retry.
Master shows the same toast (fired while the scene is still inactive, visible on unlock). The gate moves when the toast fires but doesn't change what the user sees, which is the scenario this PR sets out to fix.
Fix: before the foreground check, retry the categories that are required and not running, and check only after that attempt settles. Alternatively, skip the check for categories whose last failure is older than the current foreground session and let the retry report. BackupFailureNotificationGateTests would then need a case where the backup recovers on foreground.
Reproduction
On the iPhone 17 simulator, with a scratch-only fault hook that is not in this PR: VssBackupClient.putObject throws notConnectedToInternet while a flag file exists. LDK's VSS path is untouched.
- Backups clean, toggle Swipe Balance to Hide (SETTINGS fails), lock.
- After 32 minutes, remove the block while locked, then unlock.
PR c58ffc3:
[13:17:19.937 UTC] INFO: Scene phase changed: inactive - AppScene
[13:17:20.670 UTC] INFO: Scene phase changed: active - AppScene
[13:17:20.671 UTC] WARN: Backup failed for more than 30 minutes - BackupService [BackupService.swift: showBackupFailureNotification() line: 774]
[13:17:26.275 UTC] DEBUG: Backup starting for: 'BLOCKTANK' - BackupService
(no 'SETTINGS' line until the cold launch at 13:20:16, which retries and succeeds by 13:20:31)
master 5663543, same hook and steps:
[13:53:33.679 UTC] WARN: Backup failed for more than 30 minutes - BackupService [showBackupFailureNotification(currentTime:)]
[13:53:34.027 UTC] INFO: Scene phase changed: inactive - AppScene
[13:53:34.480 UTC] INFO: Scene phase changed: active - AppScene
[13:53:40.340 UTC] DEBUG: Backup starting for: 'BLOCKTANK' (SETTINGS not retried)
There was a problem hiding this comment.
Fixed in 06824ec. Foregrounding now retries required, idle app-managed backups before checking for overdue failures. Failure notifications are suppressed until that recovery pass settles, and running or scheduled backups are not duplicated. LDK-managed backups remain with ldk-node.
Retries preserve the original pending timestamp and the existing ten-minute notification cooldown: a successful retry produces no stale toast, while a still-failing overdue backup remains eligible for an alert. Cancelled recovery also retains the pending requirement.
Added service-level regression coverage for recovery across all app-managed categories, continued failures and cooldown, concurrent/scheduled backups, cancellation, and restore/wipe suppression. All 30 focused backup tests pass on a separate simulator. These use isolated backup data and backdated pending timestamps; the actual device lock/unlock scenario still needs rerunning on this commit. PR scope and QA notes are updated.
There was a problem hiding this comment.
06824ec looks right in code:
setAppActive(true)opens a recovery window, awaitsretryPendingBackups()for required idle categories, and only then runscheckForFailedBackups();requiredAtStartkeeps the original age, so a retry that still fails is reported.
I'm leaving this open until the lock/unlock re-run on the device, and I'm checking one edge (a retry that never returns) separately.
jvsena42
left a comment
There was a problem hiding this comment.
Delta at 06824ec: the MEDIUM is fixed in code (trace on the thread). One new LOW is inline, introduced by the recovery window.
Checked and clean:
- One retry per foreground, guarded by
isRecovering, and suppression doesn't use up the cooldown. - No duplicate concurrent backups for a category: retry registration and
triggerBackupshare the serial backup queue. - A failed retry keeps the original
requiredage, so a real failure is still reported. - Cold launch is unchanged.
- No lock-order inversion between
stateQueueandstatusUpdateQueue. - Cancelling now keeps
required.
Perf-relevant: each switch back to active starts one upload per required idle category. I'll count Backup starting for: lines per Scene phase changed: active on the device.
Device gate (partial): 06824ec — not run yet. Pending: Manual Test 1 (recovery on unlock), the real-failure-still-reported case, and Manual Test 2 (active regression), with the same fault hook as before. Device results will follow in a separate review.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review of changes since c58ffc3, including the affected backup paths, at 317736d. Inherited baseline: QA review, covering AppScene scene-phase gating, the cold-launch order and offline skip, and Android BackupRepo.checkForFailedBackups at 31b05e7.
No new actionable code findings.
Foregrounding retries required, idle, app-managed backups before the overdue check. The check still runs only when the scene is active and backup observation has started. Alert suppression ends when those retries finish or after 60 seconds, and the uploads keep running. A backup that is still running stays out of the failure predicate, so another overdue failure can be reported once the window ends. Retries keep the original pending timestamp. The ten-minute cooldown is left unconsumed while the window is open. Cancelling recovery keeps the pending requirement and does not upload.
The stale unlock toast reported on c58ffc3 in the device thread is the path this recovery targets. That walkthrough has not been repeated on this commit. The open-window hang described in the later thread is bounded by waitForForegroundRetries.
Android checkForFailedBackups at 31b05e7 still reports an overdue required backup from its periodic check, with no scene gate, running exclusion, or foreground retry. That difference is the iOS fix.
Validation: Source was inspected at 317736d. BackupForegroundRecoveryTests and BackupFailureNotificationGateTests were read and were not executed in this review. Run Tests succeeded on this commit. Run Integration Tests was canceled at its one-hour limit after unrelated UTXO and Blocktank failures, so it is not evidence for this gate. The author reports the focused backup tests passed on a simulator (comment); that report is not a test performed in this review.
Device testing: not performed in this review. The failed Manual Test 1 result belongs to c58ffc3, not this commit.
Ready for device testing.
Suggested additional test cases
- iOS. Wallet running, with overdue settings and metadata backups. Settings upload fails immediately and metadata upload stays open for more than one minute. Foreground Bitkit. The settings failure toast appears while metadata is still uploading, and the metadata upload is not cancelled. When metadata later succeeds, the settings backup remains outstanding.
- iOS. Kill Bitkit with an overdue idle app-managed backup, then relaunch online. The splash and PIN screen stay free of a backup-failure toast before the launch retry. If that retry succeeds, no toast appears. If it fails and the backup is still required and not running, the toast appears after that attempt.
Fixes #562
This PR prevents background backup checks from surfacing a stale error toast when Bitkit is unlocked.
Description
Out of Scope
Design
N/A — no design available.
Preview
N/A — behavior-only change.
QA Notes
Journeys
N/A — not drivable; see Manual Tests.
Manual Tests
Automated Checks
BackupFailureNotificationGateTests.swift— covers inactive suppression, active and recovered backup states, foreground recovery suppression, safe wall-clock comparisons and notification cooldown behavior.BackupForegroundRecoveryTests.swift— covers recovery of all app-managed categories without a stale alert, genuine failures retaining their age and cooldown, no duplicate running or scheduled uploads, cancellation retaining pending requirements, suppression during restore/wipe or without observation, stalled retries not hiding other failures, and late completion not releasing a newer recovery window.