Repository navigation
fix: back off pending migration metadata retries - #1462
Conversation
|
talosmachina
left a comment
There was a problem hiding this comment.
No findings. Gates the post-migration retry on each wallet sync behind a persisted backoff (1 min doubling to 1 h, reset on clock rollback, cleared with the rest of the RN migration data). Reviewed 654a0bf, full tier, reasoned from the code: this Android branch was not built here, and CI was still running at review time.
What I checked, and 5 candidates I ruled out
Read in full: RNMigrationRetrySchedule, shouldRetryPendingMigrationData, recordPendingMigrationRetry, reapplyMetadataAfterSync, clearPersistedMigrationData, canCleanupAfterMigration in MigrationService.kt. handleSyncCompleted, retryPendingMigrationData, completeRNRemoteBackupRestore, finishMigrationSuccessfully and finishMigrationWithFallbackSync in AppViewModel.kt.
Call sites traced: reapplyMetadataAfterSync (four callers; only the sync-retry path is gated, so initial migration and remote restore still apply at once), hasPendingMigrationRetries (now only reached through the new gate).
CI: pending at review time (build, build-local, lint).
Ruled out
- Schedule left behind after recovery:
recordPendingMigrationRetryremoves the key whencanCleanupAfterMigrationis true, andclearPersistedMigrationDataremoves it too. - Backoff starts before the first retry: the schedule is written only at the end of
reapplyMetadataAfterSync, so the initial migration records a 1 min wait and the first gated retry comes after it, as the PR describes. - Exception mid-reapply skips the record: the schedule is then missing or unchanged, so the next sync retries as
masterdoes today. No regression. - Blocktank order retries slowed too: the gate covers every pending item behind
canCleanupAfterMigration, so a failed order fetch now retries on the same backoff. The data stays persisted and is still recovered, so the only effect is a later retry. - Mock stub with a default argument:
shouldRetryPendingMigrationData(any())matches the$defaultcall, because the defaultnowis computed before the mocked instance method runs.
Merge confidence: 4/5, no findings, but CI had not finished and the branch was not built here.
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
jvsena42
left a comment
There was a problem hiding this comment.
Code scan at b169c41: no findings. Device result follows.
Checked:
- Schedule:
afterAttemptgives 60, 120, 240, 480, 960, 1920, 3600 s and stays at 3600; a marker is never dropped, so the floor is one pass per hour (MigrationService.kt:82-96). - Gate: only
retryPendingMigrationDatais gated (AppViewModel.kt:1971); the completion branches still run immediately.recordPendingMigrationRetryis the last statement ofreapplyMetadataAfterSync, so all four callers record. - Persistence: the schedule is read from the DataStore on every check, with no in-memory copy, so a relaunch keeps the deadline. A corrupt or missing value reads as due and is overwritten with a valid 60 s schedule. A clock moved backwards grants one pass and keeps doubling from the stored delay.
- Cleared on a clean pass (
:393-394), inclearPersistedMigrationData(:363) and therefore on wallet wipe (WipeWalletUseCase.kt:97). - A cancelled pass writes no schedule (the record is the last statement), so the next sync re-runs it. Two passes cannot interleave:
isCompletingMigrationis set with no suspension after the branch test. - On
masterthe Android retry never rebuilt payment history or marked activities seen, so the per-pass ACTIVITY backup measured on synonymdev/bitkit-ios#852 has no Android counterpart. What was unbounded here was the pass itself: a DataStore write every 10 s, and a Blocktank fetch when order ids are pending. AppViewModelSendFlowTest.ktonly moves the stub to the new gate and adds the six-sync test; no send-path code changes.
perf: relevant (re-times repeated work).
Device gate (partial): b169c41 — not run yet; pending the pass rate with one unresolvable marker, 3 minutes idle and across a relaunch, on this build and on master.
jvsena42
left a comment
There was a problem hiding this comment.
Approving at b169c41. The code scan is in my earlier review today; this is the device result.
Device gate: b169c41 — journeys: none listed; perf: 4 metadata retry passes in 8 min 20 s with one unresolvable marker (master: 20 in 200 s, one per wallet sync), gaps 68 s / 120 s / 248 s; after a force-stop and relaunch 3 s into the 240 s wait, 24 wallet syncs passed with no retry and the pass ran 8 s after the stored deadline; stored schedule delayMillis: 480000 at the end.
Measured on a Pixel 9 emulator (regtest, 41 payments), app idle on Home, with rnPendingMetadata holding one tag for an activity id that is not in the wallet, written straight into the rn_migration DataStore.
Log lines (UTC)
master, same marker: one pass per sync for the whole window.
13:21:00.578 WARN [MigrationService.kt:989] Activity not found for tags: id='0000000014621462…ffffffff'
13:21:10.112 WARN [MigrationService.kt:989] Activity not found for tags: id='0000000014621462…ffffffff'
13:21:20.123 WARN [MigrationService.kt:989] Activity not found for tags: id='0000000014621462…ffffffff'
13:21:30.173 WARN [MigrationService.kt:989] Activity not found for tags: id='0000000014621462…ffffffff'
This PR:
13:23:40.038 INFO [MigrationService.kt:1810] Applying remote metadata (tags: 1)
13:24:48.489 INFO [MigrationService.kt:1810] Applying remote metadata (tags: 1)
13:26:48.494 INFO [MigrationService.kt:1810] Applying remote metadata (tags: 1)
13:26:51 force-stop, relaunch 13:26:54
13:30:56.054 INFO [MigrationService.kt:1810] Applying remote metadata (tags: 1)
Skipped — covered by CI at b169c41: unit tests, detekt, lint, e2e (all green).
One item outside this PR's scope is in a separate comment.
|
Outside this PR's scope, no change needed here. Found while measuring the retry rate; filed as #1464. The ACTIVITY backup is uploaded after every wallet sync, about every 10 s, while the app sits idle on Home.
Reproduction: device log, idle on Home (UTC)Pixel 9 emulator, regtest, 41 payments, nothing sent or received: To repeat it: open the app on a wallet with a backup configured, leave it on Home for two minutes and count The iOS side had the same shape on its retry path ( |
Related: synonymdev/bitkit-ios#852
Follow-up to #1396.
This PR slows automatic retries for unresolved React Native activity metadata while preserving recovery when the activity later arrives. Android already avoids rebuilding full payment history on this path; iOS #852 receives the matching schedule and also removes its background history rebuild.
Description
Out of Scope
Design
N/A — no UI changes.
Preview
N/A
QA Notes
Journeys
N/A — not drivable; see Manual Tests.
Manual Tests
Automated Checks
RNMigrationRetryScheduleTest.kt— serialization/reload, repeated sync rejection, doubling/hourly cap and clock rollbackRNMigrationRetryPersistenceTest.kt— a real temporary DataStore is closed and reopened with a recreated service; saved delays block early retries, late tags recover, and recovery/cleanup removes the scheduleAppViewModelSendFlowTest.kt— due retries preserve existing unseen/history/node protections; six syncs during backoff do no metadata reapplication or payment-history rebuildLocal validation at
635913d3c: development compilation, all 3,951 unit tests and detekt passed.Independent full review covered the iOS and Android local fixes with no actionable findings. The service-level DataStore restart/recovery/cleanup follow-up was independently reviewed and tested. The proposed populated-wallet/device backup measurement remains unrun.