Skip to content

fix: prevent MMKV/AsyncStorage split-brain in podcast cache and reading progress (#1953)#2090

Open
vedant7007 wants to merge 1 commit into
SB2318:mainfrom
vedant7007:fix/mmkv-split-brain-1953
Open

fix: prevent MMKV/AsyncStorage split-brain in podcast cache and reading progress (#1953)#2090
vedant7007 wants to merge 1 commit into
SB2318:mainfrom
vedant7007:fix/mmkv-split-brain-1953

Conversation

@vedant7007

Copy link
Copy Markdown
Contributor

closes #1953

the bug

in MMKVUtils.ts, when an mmkv write threw, the catch block just logged and fell through to AsyncStorage. but on the very next read the code preferred mmkv → returned stale data. so a user could add or remove a downloaded podcast, see it succeed in the UI, restart the app, and the change would silently disappear.

reading progress had a related issue: setReadingProgressItem called mmkv?.set() with no try/catch at all. if mmkv threw, the exception propagated up into saveProgress → uncaught.

what changed

MMKVUtils.ts

  • added a session-level unhealthy flag per store (podcastMMKVUnhealthy, readingProgressMMKVUnhealthy). once we detect an mmkv write failure we:
    1. attempt to invalidate the stale mmkv key with a best-effort delete
    2. mark the store unhealthy so subsequent reads/writes skip mmkv entirely
    3. write through to AsyncStorage
  • on the read side, when mmkv is healthy but returns nothing, we now cross-check AsyncStorage. this recovers data written during a failed-mmkv session so users dont see phantom-empty lists
  • wrapped setReadingProgressItem, getReadingProgressItem, deleteReadingProgressItem in try/catch. reading progress fallbacks go to AsyncStorage under a reading_progress: prefix so we dont collide with anything else in AsyncStorage
  • deleteItem and clearMMKV now always attempt AsyncStorage cleanup even if mmkv throws, so a partial failure cant leave stale data
  • guarded all internal console.* with __DEV__ — no more log leakage in production builds

MMKVUtils.test.ts (new)
covers the split-brain scenarios end-to-end:

  • healthy read/write goes through mmkv only
  • mmkv write throws → mmkv key invalidated, AsyncStorage receives the write, next read returns fresh data
  • mmkv returns empty but AsyncStorage has data → returns the AsyncStorage value
  • mmkv read throws → falls back to AsyncStorage
  • reading progress previously-uncaught throw is now caught
  • delete paths hit both stores
  • module-unavailable path uses AsyncStorage directly

testing done

  • added 11 new unit tests exercising the healthy path, both failure paths, and the recovery scenarios
  • tested locally by triggering mmkv.set throw via the mock — retrieval now returns the just-written data instead of stale mmkv data
  • verified existing ReadingProgressService.test.ts still passes since the public api signatures didnt change

checklist

  • closes referenced issue
  • no breaking changes to public exports
  • added tests for new behavior
  • no console.log in production paths
  • follows existing code style

…ng progress (SB2318#1953)

when mmkv writes throw, the previous code silently fell through to AsyncStorage but subsequent reads still went to MMKV first — returning stale data and causing state to appear to revert.

changes to MMKVUtils.ts:
- track a session-level unhealthy flag per store; once mmkv write throws we invalidate the stale mmkv key and route all further reads/writes to AsyncStorage
- read side now cross-checks: if mmkv returns nothing but AsyncStorage has data (from a prior failed-write fallback), we use the AsyncStorage value instead of falsely reporting empty
- reading progress functions previously called mmkv.set with no try/catch — an mmkv throw would crash the caller. now wrapped, with AsyncStorage fallback under a namespaced prefix so reads can find fallback values
- deleteItem and clearMMKV now always attempt AsyncStorage cleanup even if mmkv throws, so a partial failure cannot leave stale data behind
- console.log guarded with __DEV__ so error details dont surface in production

added MMKVUtils.test.ts covering:
- healthy read/write path
- write-throws → invalidation + AsyncStorage fallback + subsequent reads return fresh data
- mmkv empty but AsyncStorage has data → returns AsyncStorage data
- mmkv read throws → falls back to AsyncStorage
- reading progress previously-uncaught throw no longer propagates
- delete paths hit both stores
- module-unavailable path uses AsyncStorage directly
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Thank you @, for creating the PR and contributing to our UltimateHealth project 💗.
Our team will review the PR and will reach out to you soon! 😇
Make sure that you have marked all the tasks that you are done with ✅.
Thank you for your patience! 😀

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Automated Review Feedback

No major issues were identified during this review.

The implementation appears consistent with the repository standards and the modified files were reviewed successfully.

Maintainer Note:

Maintainer @SB2318 will review this pull request after the initial automated review cycle is complete.

@vedant7007

Copy link
Copy Markdown
Contributor Author

hey @SB2318 heads up on CI:

  • Install Dependencies / Knip Dead Code — failing because frontend/yarn.lock is out of sync with package.json (yarn install --frozen-lockfile rejects). same failure is showing up on my other open PR fix: handle storage read errors in OfflinePodcastList with error UI and retry (#1954) #2041 and unrelated PRs on the branch, so this looks like a pre-existing repo issue from a recent package.json update that landed without regenerating yarn.lock. i havent touched package.json or the lockfile in this PR — only frontend/src/helper/MMKVUtils.ts and its test file
  • PR Governance / labels — could you drop a label on this PR when you get a sec? i dont have permissions to self-label. gssoc + mobile-task + bug would fit

happy to help debug the lockfile in a separate PR if that's useful. thanks!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Data-integrity] MMKV write failures silently fall through to AsyncStorage — split-brain causes stale reads

1 participant