Skip to content

Conversation

@Gudahtt
Copy link
Member

@Gudahtt Gudahtt commented Jul 18, 2025

Description

Prevent frequent high-volume writes caused by state updates from the CronjobController. The controller state includes an event with a timestamp that can be updated very frequently if the user has a snap installed with a scheduled recurring event with a short interval. It can even run while the wallet is otherwise idle, causing the entirety of wallet state to be written to disk repeatedly.

We have temporarily mitigated the problem by moving the CronjobController state to a new top-level storage key, so that it can be written to independently of other state. This controller's state is very small and should not cause any significant amount of write volume.

Open in GitHub Codespaces

Changelog

CHANGELOG entry: Prevent frequent writes while the wallet UI is closed

Related issues

Fixes: #33879

Manual testing steps

To test that cronjobs work correctly:

  1. Go to test-snaps
  2. Install "Cronjobs Snap".
  3. Check if dialog is triggered every 10(?)s. We should update this to send notifications 😅
  4. Install "Cronjob Duration Snap".
  5. Check if notification is sent every 10(?)s.
  6. Install "Background Events Snap".
  7. Schedule a background event with a certain date or duration.
  8. Check if dialog is triggered after the expected duration.

You can restart the client in between steps to ensure persistence works

To test that write volume has been reduced:

  • Monitor disk usage while UI is closed
  • Add breakpoint in background console in persistence manager to confirm there are no writes

Screenshots/Recordings

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@sentry
Copy link

sentry bot commented Jul 18, 2025

🔍 Existing Issues For Review

Your pull request is modifying functions with the following pre-existing issues:

📄 File: app/scripts/background.js

Function Unhandled Issue
initialize TypeError: Cannot read properties of undefined (reading 'getAll') /sc...
Event Count: 1 Affected Users: 0

Did you find this useful? React with a 👍 or 👎

@metamaskbot
Copy link
Collaborator

metamaskbot commented Jul 18, 2025

✨ Files requiring CODEOWNER review ✨

🫰 @MetaMask/snaps-devs (2 files, +17 -1)
  • 📁 app/
    • 📁 scripts/
      • 📁 controller-init/
        • 📁 snaps/
          • 📄 cronjob-controller-init.test.ts +7 -0
          • 📄 cronjob-controller-init.ts +10 -1

Gudahtt and others added 3 commits July 18, 2025 15:36
Prevent frequent high-volume writes caused by state updates from the
CronjobController. The controller state includes an event with a
timestamp that can be updated very frequently if the user has a snap
installed with a scheduled recurring event with a short interval. It
can even run while the wallet is otherwise idle, causing the entirety
of wallet state to be written to disk repeatedly.

We have temporarily mitigated the problem by moving the
CronjobController state to a new top-level storage key, so that it
can be written to independently of other state. This controller's state
is very small and should not cause any significant amount of write
volume.
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

This patches the cronjob controller to add `stateManager` support.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/34414?quickstart=1)

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry:

## **Related issues**

Fixes:

## **Manual testing steps**

1. Go to this page...
2.
3.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
@Gudahtt Gudahtt force-pushed the fix-frequent-writes-while-idle branch from ff0a241 to 826ea86 Compare July 18, 2025 18:06
Mrtenz
Mrtenz previously approved these changes Jul 18, 2025
@metamaskbot
Copy link
Collaborator

Builds ready [1b2abad]
UI Startup Metrics (1238 ± 52 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1238114314235212781324
load106196611954811001139
domContentLoaded105392811885010941134
domInteractive18135591642
firstPaint68579120042610661114
backgroundConnect21019734515212225
firstReactRender21154462236
getState1045281126
initialActions30255516
loadScripts85172698349892929
setupStore74172712
WebpackHomeuiStartup24121913265416725382629
load19151434228919620242245
domContentLoaded19081426228119620132241
domInteractive201390161672
firstPaint181661587155200345
backgroundConnect3111275333068
firstReactRender1639335770210308
getState184249381447
initialActions11217628829
loadScripts18981422226919420052239
setupStore206231351840
FirefoxBrowserifyHomeuiStartup14971307192412915561792
load1290113114948213481446
domContentLoaded1289113114948213481446
domInteractive1043746564106217
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect271570112856
firstReactRender28217182951
getState193299497167
initialActions508011226
loadScripts1267111814698213261424
setupStore9418519724
WebpackHomeuiStartup17781531253420918642239
load14871301194614215431808
domContentLoaded14871300194614215431807
domInteractive93344075890218
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect291776113453
firstReactRender5343139105563
getState142238311638
initialActions13124041964
loadScripts14631279191314015171780
setupStore2242895416113
Benchmark value 1238 exceeds gate value 1234 for chrome browserify home mean uiStartup
Benchmark value 210 exceeds gate value 10 for chrome browserify home mean backgroundConnect
Benchmark value 4 exceeds gate value 1 for chrome browserify home mean initialActions
Benchmark value 851 exceeds gate value 830 for chrome browserify home mean loadScripts
Benchmark value 43 exceeds gate value 41 for chrome browserify home p95 domInteractive
Benchmark value 225 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 16 exceeds gate value 1.2 for chrome browserify home p95 initialActions
Benchmark value 2412 exceeds gate value 2192 for chrome webpack home mean uiStartup
Benchmark value 1915 exceeds gate value 1711 for chrome webpack home mean load
Benchmark value 1908 exceeds gate value 1704 for chrome webpack home mean domContentLoaded
Benchmark value 11 exceeds gate value 7 for chrome webpack home mean initialActions
Benchmark value 1899 exceeds gate value 1699 for chrome webpack home mean loadScripts
Benchmark value 2629 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 2245 exceeds gate value 2030 for chrome webpack home p95 load
Benchmark value 2242 exceeds gate value 2005 for chrome webpack home p95 domContentLoaded
Benchmark value 72 exceeds gate value 57 for chrome webpack home p95 domInteractive
Benchmark value 345 exceeds gate value 334 for chrome webpack home p95 firstPaint
Benchmark value 29 exceeds gate value 7 for chrome webpack home p95 initialActions
Benchmark value 2239 exceeds gate value 1970 for chrome webpack home p95 loadScripts
Benchmark value 1498 exceeds gate value 1405 for firefox browserify home mean uiStartup
Benchmark value 1290 exceeds gate value 1245 for firefox browserify home mean load
Benchmark value 1290 exceeds gate value 1239 for firefox browserify home mean domContentLoaded
Benchmark value 27 exceeds gate value 25 for firefox browserify home mean backgroundConnect
Benchmark value 29 exceeds gate value 25 for firefox browserify home mean firstReactRender
Benchmark value 19 exceeds gate value 11 for firefox browserify home mean getState
Benchmark value 5 exceeds gate value 1 for firefox browserify home mean initialActions
Benchmark value 1267 exceeds gate value 1230 for firefox browserify home mean loadScripts
Benchmark value 10 exceeds gate value 9 for firefox browserify home mean setupStore
Benchmark value 1792 exceeds gate value 1660 for firefox browserify home p95 uiStartup
Benchmark value 217 exceeds gate value 195 for firefox browserify home p95 domInteractive
Benchmark value 167 exceeds gate value 24 for firefox browserify home p95 getState
Benchmark value 26 exceeds gate value 2 for firefox browserify home p95 initialActions
Benchmark value 1779 exceeds gate value 1615 for firefox webpack home mean uiStartup
Benchmark value 1488 exceeds gate value 1380 for firefox webpack home mean load
Benchmark value 1487 exceeds gate value 1380 for firefox webpack home mean domContentLoaded
Benchmark value 29 exceeds gate value 26 for firefox webpack home mean backgroundConnect
Benchmark value 54 exceeds gate value 38 for firefox webpack home mean firstReactRender
Benchmark value 13 exceeds gate value 1 for firefox webpack home mean initialActions
Benchmark value 1463 exceeds gate value 1360 for firefox webpack home mean loadScripts
Benchmark value 23 exceeds gate value 13 for firefox webpack home mean setupStore
Benchmark value 2239 exceeds gate value 1935 for firefox webpack home p95 uiStartup
Benchmark value 1808 exceeds gate value 1660 for firefox webpack home p95 load
Benchmark value 1807 exceeds gate value 1660 for firefox webpack home p95 domContentLoaded
Benchmark value 218 exceeds gate value 156 for firefox webpack home p95 domInteractive
Benchmark value 53 exceeds gate value 49 for firefox webpack home p95 backgroundConnect
Benchmark value 63 exceeds gate value 50 for firefox webpack home p95 firstReactRender
Benchmark value 38 exceeds gate value 32 for firefox webpack home p95 getState
Benchmark value 64 exceeds gate value 2 for firefox webpack home p95 initialActions
Benchmark value 1780 exceeds gate value 1630 for firefox webpack home p95 loadScripts
Benchmark value 113 exceeds gate value 28 for firefox webpack home p95 setupStore
Sum of mean exceeds: 1828ms | Sum of p95 exceeds: 2469.8ms
Sum of all benchmark exceeds: 4297.8ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 1.87 KiB (0.03%)
  • ui: 4 Bytes (0%)
  • common: 136 Bytes (0%)

@Gudahtt Gudahtt marked this pull request as ready for review July 18, 2025 19:31
@Gudahtt Gudahtt requested a review from a team as a code owner July 18, 2025 19:31
cursor[bot]

This comment was marked as outdated.

Mrtenz
Mrtenz previously approved these changes Jul 18, 2025
cursor[bot]

This comment was marked as outdated.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Cronjob State Loss Due to Async Storage Errors

The CronjobControllerStorageManager.set() method uses a fire-and-forget pattern for browser.storage.local.set() operations, calling it asynchronously without awaiting and silently swallowing errors with .catch(console.error). This leads to silent data loss: the CronjobController assumes state is persisted even if the storage operation fails (e.g., due to quota limits), causing in-memory state to diverge from persisted state and resulting in lost cronjob state on restart without any indication of failure.

app/scripts/lib/CronjobControllerStorageManager.ts#L49-L57

*/
set(data: Json) {
if (!this.#initialized) {
throw new Error('CronjobControllerStorageManager not yet initialized');
}
browser.storage.local
.set({ [CronjobControllerStorageKey]: data })
.catch(console.error);
}

Fix in CursorFix in Web


Was this report helpful? Give feedback by reacting with 👍 or 👎

@metamaskbot
Copy link
Collaborator

Builds ready [123a239]
UI Startup Metrics (1252 ± 71 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1252112216227112841363
load107396114016711051155
domContentLoaded106695413936810961147
domInteractive181378101641
firstPaint66467137943710851147
backgroundConnect2081982306210224
firstReactRender20154252135
getState1033371228
initialActions40436316
loadScripts864759117766892947
setupStore74172712
WebpackHomeuiStartup23261789270415624252538
load18281358237018119332123
domContentLoaded18171353215417419092101
domInteractive181270121552
firstPaint1526544969169294
backgroundConnect49104338030287
firstReactRender1698837675207333
getState193268451337
initialActions7220020628
loadScripts18101350215217419022092
setupStore156250241523
FirefoxBrowserifyHomeuiStartup14671297208114914961776
load12641127168410112981445
domContentLoaded12641127168410112971444
domInteractive1133755484100335
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2816195212853
firstReactRender27206392956
getState11325927741
initialActions506510221
loadScripts1241111216569812751405
setupStore123184211138
WebpackHomeuiStartup17721526263220018472142
load14971296200215715791854
domContentLoaded14961296200215715781854
domInteractive101333896395286
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect3120118153763
firstReactRender52416965564
getState133211231529
initialActions9121029628
loadScripts14731275196615415521826
setupStore14425935935
Benchmark value 1253 exceeds gate value 1234 for chrome browserify home mean uiStartup
Benchmark value 1074 exceeds gate value 1070 for chrome browserify home mean load
Benchmark value 1067 exceeds gate value 1061 for chrome browserify home mean domContentLoaded
Benchmark value 209 exceeds gate value 10 for chrome browserify home mean backgroundConnect
Benchmark value 4 exceeds gate value 1 for chrome browserify home mean initialActions
Benchmark value 865 exceeds gate value 830 for chrome browserify home mean loadScripts
Benchmark value 224 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 16 exceeds gate value 1.2 for chrome browserify home p95 initialActions
Benchmark value 947 exceeds gate value 940 for chrome browserify home p95 loadScripts
Benchmark value 2327 exceeds gate value 2192 for chrome webpack home mean uiStartup
Benchmark value 1829 exceeds gate value 1711 for chrome webpack home mean load
Benchmark value 1818 exceeds gate value 1704 for chrome webpack home mean domContentLoaded
Benchmark value 49 exceeds gate value 40 for chrome webpack home mean backgroundConnect
Benchmark value 8 exceeds gate value 7 for chrome webpack home mean initialActions
Benchmark value 1810 exceeds gate value 1699 for chrome webpack home mean loadScripts
Benchmark value 2539 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 2123 exceeds gate value 2030 for chrome webpack home p95 load
Benchmark value 2101 exceeds gate value 2005 for chrome webpack home p95 domContentLoaded
Benchmark value 288 exceeds gate value 90 for chrome webpack home p95 backgroundConnect
Benchmark value 28 exceeds gate value 7 for chrome webpack home p95 initialActions
Benchmark value 2092 exceeds gate value 1970 for chrome webpack home p95 loadScripts
Benchmark value 1467 exceeds gate value 1405 for firefox browserify home mean uiStartup
Benchmark value 1265 exceeds gate value 1245 for firefox browserify home mean load
Benchmark value 1264 exceeds gate value 1239 for firefox browserify home mean domContentLoaded
Benchmark value 114 exceeds gate value 110 for firefox browserify home mean domInteractive
Benchmark value 29 exceeds gate value 25 for firefox browserify home mean backgroundConnect
Benchmark value 28 exceeds gate value 25 for firefox browserify home mean firstReactRender
Benchmark value 12 exceeds gate value 11 for firefox browserify home mean getState
Benchmark value 5 exceeds gate value 1 for firefox browserify home mean initialActions
Benchmark value 1241 exceeds gate value 1230 for firefox browserify home mean loadScripts
Benchmark value 12 exceeds gate value 9 for firefox browserify home mean setupStore
Benchmark value 1776 exceeds gate value 1660 for firefox browserify home p95 uiStartup
Benchmark value 335 exceeds gate value 195 for firefox browserify home p95 domInteractive
Benchmark value 56 exceeds gate value 55 for firefox browserify home p95 firstReactRender
Benchmark value 41 exceeds gate value 24 for firefox browserify home p95 getState
Benchmark value 21 exceeds gate value 2 for firefox browserify home p95 initialActions
Benchmark value 38 exceeds gate value 27 for firefox browserify home p95 setupStore
Benchmark value 1773 exceeds gate value 1615 for firefox webpack home mean uiStartup
Benchmark value 1497 exceeds gate value 1380 for firefox webpack home mean load
Benchmark value 1497 exceeds gate value 1380 for firefox webpack home mean domContentLoaded
Benchmark value 101 exceeds gate value 100 for firefox webpack home mean domInteractive
Benchmark value 32 exceeds gate value 26 for firefox webpack home mean backgroundConnect
Benchmark value 53 exceeds gate value 38 for firefox webpack home mean firstReactRender
Benchmark value 9 exceeds gate value 1 for firefox webpack home mean initialActions
Benchmark value 1473 exceeds gate value 1360 for firefox webpack home mean loadScripts
Benchmark value 15 exceeds gate value 13 for firefox webpack home mean setupStore
Benchmark value 2142 exceeds gate value 1935 for firefox webpack home p95 uiStartup
Benchmark value 1854 exceeds gate value 1660 for firefox webpack home p95 load
Benchmark value 1854 exceeds gate value 1660 for firefox webpack home p95 domContentLoaded
Benchmark value 286 exceeds gate value 156 for firefox webpack home p95 domInteractive
Benchmark value 63 exceeds gate value 49 for firefox webpack home p95 backgroundConnect
Benchmark value 64 exceeds gate value 50 for firefox webpack home p95 firstReactRender
Benchmark value 28 exceeds gate value 2 for firefox webpack home p95 initialActions
Benchmark value 1826 exceeds gate value 1630 for firefox webpack home p95 loadScripts
Benchmark value 35 exceeds gate value 28 for firefox webpack home p95 setupStore
Sum of mean exceeds: 1428ms | Sum of p95 exceeds: 2128.8ms
Sum of all benchmark exceeds: 3556.8ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 1.89 KiB (0.03%)
  • ui: 4 Bytes (0%)
  • common: 136 Bytes (0%)

@Gudahtt Gudahtt changed the title fix: Fix frequent writes from CronjobController fix: cp-12.22.3 Fix frequent writes from CronjobController Jul 18, 2025
@DDDDDanica
Copy link
Contributor

Tested and LGTM !

@DDDDDanica DDDDDanica added this pull request to the merge queue Jul 21, 2025
Merged via the queue into main with commit f2104e9 Jul 21, 2025
403 of 408 checks passed
@DDDDDanica DDDDDanica deleted the fix-frequent-writes-while-idle branch July 21, 2025 14:11
@github-actions github-actions bot locked and limited conversation to collaborators Jul 21, 2025
@metamaskbot metamaskbot added the release-13.1.0 Issue or pull request that will be included in release 13.1.0 label Jul 21, 2025
@gauthierpetetin gauthierpetetin added release-12.22.3 Issue or pull request that will be included in release 12.22.3 and removed release-13.1.0 Issue or pull request that will be included in release 13.1.0 labels Aug 5, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-12.22.3 Issue or pull request that will be included in release 12.22.3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Abnormal disk writing, 5 mb/s 24/7

6 participants