Skip to content

Mitigate sync settings UI race condtions - #9316

Merged
MiSikora merged 2 commits into
developfrom
feature/msikora/simple-sync/race-conditions
Jul 30, 2026
Merged

Mitigate sync settings UI race condtions#9316
MiSikora merged 2 commits into
developfrom
feature/msikora/simple-sync/race-conditions

Conversation

@MiSikora

@MiSikora MiSikora commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/1216103556496795/task/1216942050413056?focus=true
Tech Design URL (if applicable): https://app.asana.com/1/137249556945/project/1216103556496795/task/1216509582049467?focus=true
API Proposals URL(s) (if applicable): N/A

Description

I've been running into inconsistencies in the new simplified sync flow (V2). Editing a device, removing a device, or turning sync off happens on a separate screen, and coming back to the main sync settings screen also kicks off a device list refresh. That refresh raced with the updates made by the action itself, so the screen would show stale or wrong data until the next refresh sorted it out.

The root cause is that the screen's view state gets written from several coroutines at once (the sync state monitor, device fetches, and user actions), and the old read-then-write pattern let one update silently overwrite another. This PR makes those updates atomic instead, behind a feature flag.

  • Every view state write in SyncActivityViewModel now goes through one updateViewState helper that takes a function transforming the current state.
  • With the new updateSyncActivityViewStateAtomically flag on, the helper uses MutableStateFlow.update so nothing gets lost. With the flag off, it keeps the old read-then-write behavior.
  • The signed-in state is no longer built from one snapshot of the current state. The suspending reads (signed-in status, this connected device, the auto-restore preference) happen first, and only then is the change applied on top of whatever the latest state is. As before, the state falls back to signed out when the connected device can't be retrieved.

Steps to test this PR

Sync Settings regression

  • Open Settings.
  • Tap "Sync & Backup".
  • Enable sync on this device.
  • Verify the "Synced Devices" section shows this device.

Device management

  • Connect a second device to the same sync account.
  • Verify the second device appears in the "Synced Devices" list.
  • Rename the second device.
  • Verify the updated name shows in the list.
  • Remove the second device.
  • Verify it disappears from the list.

Turn off sync

  • Tap "Turn Off Sync & Backup…".
  • Confirm with "Turn Off".
  • Verify the screen returns to the signed-out state.

Flag disabled

  • Disable the updateSyncActivityViewStateAtomically flag in the FF inventory.
  • Repeat the scenarios above.
  • Verify the screen behaves the same way.

UI changes

N/A


Note

Medium Risk
Changes concurrency semantics for sync settings UI state across many code paths; rollout is gated by an internal feature flag with legacy behavior when off.

Overview
Addresses stale or wrong sync settings UI when device list refreshes race with edits, removals, or turn-off actions from other screens. Concurrent coroutines (sync state monitor, device fetches, user actions) could overwrite each other via read-then-write on viewState.

Changes: All SyncActivityViewModel view-state writes go through a single updateViewState helper. With updateSyncActivityViewStateAtomically enabled (new SyncFeature toggle, default INTERNAL), updates use MutableStateFlow.update; when disabled, behavior stays the old assign-after-read path.

Signed-in state is rebuilt via updateSignedInState: repository reads (signed-in, this device, auto-restore) run first, then the lambda applies on the latest state—still falling back to signed-out if the connected device is missing. Tests wire the fake feature toggle with atomic updates on.

Reviewed by Cursor Bugbot for commit e6d6b41. Bugbot is set up for automated code reviews on this repo. Configure here.

@Toggle.DefaultValue(DefaultFeatureValue.FALSE)
fun useSimplifiedSync(): Toggle

@Toggle.DefaultValue(DefaultFeatureValue.INTERNAL)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the plan for enabling? Should it just default to TRUE now, and can use the remote config to disable it if there are problems in the wild?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I didn't think of handling it this way. Thanks!

MiSikora commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Jul 30, 10:42 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 30, 10:42 AM UTC: @MiSikora merged this pull request with Graphite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants