metamask-extension icon indicating copy to clipboard operation
metamask-extension copied to clipboard

fix: prevent memory leaks in `useAsync` hooks

Open MajorLift opened this issue 8 months ago • 8 comments

Description

Implements manual, explicit management of cache/state cleanup in useAsync{Callback,Result} instead of relying on React hooks. The motivation is to prevent memory leaks especially from large/frequent fetch requests.

(This only pertains to memory managed by react, not e.g. IndexedDB buildup due to fetchWithCache calls or background caching.)

  • Cleanup function guarantees that e.g. fetch results are not kept in closure causing memory leaks.
  • Wrap return array in useMemo.
    • Mitigates issue of every asyncFn/execute call allocating a new output object/array and holding onto the reference, potentially leading to memory leaks.
    • Prevent re-renders if result object reference has changed but its properties haven't.
  • Executing asyncFnRef.current instead of asyncFn, and then explicitly cleaning it up ensures gc of long-lived async operations that could potentially hold on to component data references after unmount.

Optimizations:

  • The reference to the execute callback returned by useAsyncCallback never needs to change if both itself and the reference to asyncFn (its only reactive variable that is external to the hook) are managed with refs.
  • asyncFn, deps passed to useAsyncCallback change reference on every re-render. Use refs to maintain stable references throughout lifecycle, and only update if deps have changed.

Once we get to React v19, we may consider migrating entirely to use{,Transition,Optimistic,ActionState,FormStatus}, under the assumption that they provide safer memory management.

Open in GitHub Codespaces

Related issues

  • https://github.com/MetaMask/metamask-extension/issues/33466
    • https://github.com/MetaMask/metamask-extension/pull/33579
    • https://github.com/MetaMask/metamask-extension/pull/33577

Manual testing steps

  1. Go to this page...

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.

MajorLift avatar Jun 12 '25 22:06 MajorLift

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

github-actions[bot] avatar Jun 12 '25 22:06 github-actions[bot]

Builds ready [dbd3854]
UI Startup Metrics (1219 ± 64 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1219109113926412581356
load106094112036210881195
domContentLoaded105393311976210811189
domInteractive17143441631
firstPaint79895120539710711186
backgroundConnect74325721
firstReactRender21144762035
getState1463371927
initialActions001000
loadScripts80769894560834928
setupStore85172813
WebpackHomeuiStartup20191454251824121692442
load15731120192619117121865
domContentLoaded15671115191819017081856
domInteractive151165101346
firstPaint1506726341164229
backgroundConnect2312373362231
firstReactRender13445354107117346
getState1042641118
initialActions214133
loadScripts15641109190718917041845
setupStore4873249617310
FirefoxBrowserifyHomeuiStartup1308117015328113491479
load1163102713807812111325
domContentLoaded1163102713807812101325
domInteractive993416625111145
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20135362128
firstReactRender23212812325
getState84385811
initialActions001001
loadScripts1146101713587711951301
setupStore74386611
WebpackHomeuiStartup15011338177010515691736
load13051155159610713671552
domContentLoaded13051155159610713671551
domInteractive81331812087121
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect21158072226
firstReactRender40294734246
getState94316929
initialActions002111
loadScripts12871140157810813491534
setupStore8578789
Benchmark value 1195 exceeds gate value 1190 for chrome browserify home p95 load
Benchmark value 1189 exceeds gate value 1180 for chrome browserify home p95 domContentLoaded
Benchmark value 1186 exceeds gate value 1180 for chrome browserify home p95 firstPaint
Benchmark value 22 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 49 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 310 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 40 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 19ms | Sum of p95 exceeds: 269ms
Sum of all benchmark exceeds: 288ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 0 Bytes (0%)
  • ui: 527 Bytes (0.01%)
  • common: 264 Bytes (0%)

metamaskbot avatar Jun 12 '25 23:06 metamaskbot

Builds ready [80becf1]
UI Startup Metrics (1225 ± 61 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1225112814636112551347
load106093912896310951169
domContentLoaded105393312826210891164
domInteractive17143841730
firstPaint81081129439710821169
backgroundConnect84356825
firstReactRender2315134142248
getState1567192032
initialActions001000
loadScripts804683100760840910
setupStore95295920
WebpackHomeuiStartup21631707259721523042562
load16851322202217817951986
domContentLoaded16791318201817717881976
domInteractive171194131354
firstPaint1546140560183265
backgroundConnect21104062433
firstReactRender15546372112305350
getState185325431425
initialActions317144
loadScripts16761316201617617841964
setupStore3063236519310
FirefoxBrowserifyHomeuiStartup1317117315348913801505
load1169101113948912111366
domContentLoaded1169101013948912111366
domInteractive973518526110142
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2213105142049
firstReactRender23205552427
getState7438388
initialActions001001
loadScripts114999613608611921334
setupStore64394612
WebpackHomeuiStartup15091325178210615961698
load13171145159811214181525
domContentLoaded13161144159811214181524
domInteractive80321751888116
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20164142126
firstReactRender40304634345
getState85304810
initialActions002111
loadScripts12981128158111214021508
setupStore7525279
Benchmark value 811 exceeds gate value 800 for chrome browserify home mean firstPaint
Benchmark value 25 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 48 exceeds gate value 45 for chrome browserify home p95 firstReactRender
Benchmark value 20 exceeds gate value 17 for chrome browserify home p95 setupStore
Benchmark value 2562 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 310 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 41 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 14ms | Sum of p95 exceeds: 366ms
Sum of all benchmark exceeds: 380ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 0 Bytes (0%)
  • ui: 490 Bytes (0.01%)
  • common: 264 Bytes (0%)

metamaskbot avatar Jun 12 '25 23:06 metamaskbot

Builds ready [edc8ceb]
UI Startup Metrics (1206 ± 78 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1206108817057812451337
load105192915117410851161
domContentLoaded104592215037510791152
domInteractive17135061630
firstPaint775134150940910761160
backgroundConnect84265724
firstReactRender20164252037
getState1352661925
initialActions001001
loadScripts801684124473841909
setupStore85182813
WebpackHomeuiStartup20631656249021521932430
load16051216190617617361859
domContentLoaded15991208189717617311848
domInteractive161161101348
firstPaint1566130549188255
backgroundConnect2511425412435
firstReactRender1194535188118321
getState144304301222
initialActions512432434
loadScripts15961205188617417281837
setupStore5373159819311
FirefoxBrowserifyHomeuiStartup1307116515767213411447
load1166103514297412071308
domContentLoaded1166103514297412061308
domInteractive993619126110145
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect19135152028
firstReactRender22203622326
getState8463689
initialActions001001
loadScripts1149102114017211911290
setupStore74365613
WebpackHomeuiStartup1475135019269615061657
load1284117117139413161465
domContentLoaded1284117117129413151465
domInteractive77531261483107
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20154042128
firstReactRender40294834245
getState85325917
initialActions002111
loadScripts1266115516859412961449
setupStore7511179
Benchmark value 24 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 53 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 311 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 40 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 23ms | Sum of p95 exceeds: 252ms
Sum of all benchmark exceeds: 275ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 0 Bytes (0%)
  • ui: 483 Bytes (0.01%)
  • common: 264 Bytes (0%)

metamaskbot avatar Jun 13 '25 00:06 metamaskbot

Builds ready [55c7c3b]
UI Startup Metrics (1227 ± 63 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1227110713706312691338
load106695712136110981182
domContentLoaded105995312066210891178
domInteractive17133241729
firstPaint698132120743410811182
backgroundConnect84396724
firstReactRender20163632025
getState1563882028
initialActions001001
loadScripts81270995461846927
setupStore85223813
WebpackHomeuiStartup20551613267423121892451
load15881271203918016941902
domContentLoaded15831267202717916901899
domInteractive161181121347
firstPaint1536031455177275
backgroundConnect20104162235
firstReactRender13946367107138344
getState144310301219
initialActions315134
loadScripts15801266201617716881897
setupStore4963189418311
FirefoxBrowserifyHomeuiStartup1310114715757213531446
load1163102413947012011316
domContentLoaded1163102413937012011316
domInteractive943618522104135
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2014104112047
firstReactRender23212822327
getState749178
initialActions001001
loadScripts1145100913266811871293
setupStore64243611
WebpackHomeuiStartup1520136617839215901707
load1326118815879313911518
domContentLoaded1326118715879313911518
domInteractive80631481386104
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect19152822123
firstReactRender41276044345
getState85304810
initialActions001011
loadScripts1308117115699413741503
setupStore8531489
Benchmark value 1182 exceeds gate value 1180 for chrome browserify home p95 firstPaint
Benchmark value 25 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 49 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 311 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 41 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 20ms | Sum of p95 exceeds: 255ms
Sum of all benchmark exceeds: 275ms

Bundle size diffs
  • background: 0 Bytes (0%)
  • ui: 531 Bytes (0.01%)
  • common: 0 Bytes (0%)

metamaskbot avatar Jun 13 '25 05:06 metamaskbot

Builds ready [bf70edd]
UI Startup Metrics (1203 ± 64 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1203109213756412511304
load104993412216310931144
domContentLoaded104393012186310891138
domInteractive17133651631
firstPaint746128120740410691119
backgroundConnect74254720
firstReactRender19136452024
getState1363471827
initialActions001001
loadScripts79969098063849899
setupStore74152713
WebpackHomeuiStartup20471646263522421592450
load16081256215319717531942
domContentLoaded16031252214819617491933
domInteractive161266111350
firstPaint1576237257187285
backgroundConnect2313336322234
firstReactRender1244637410496362
getState1052631116
initialActions513023033
loadScripts16001251213319517471922
setupStore4773189317314
FirefoxBrowserifyHomeuiStartup1331118916658113761474
load1185104315417912281312
domContentLoaded1184104315417912271312
domInteractive983620729111150
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2113103112138
firstReactRender23203022329
getState7536389
initialActions001001
loadScripts1165102815177912041299
setupStore5411169
WebpackHomeuiStartup15351315177811216171738
load13441148159111514381559
domContentLoaded13441148159011414371559
domInteractive79431741585106
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20154232126
firstReactRender41305744347
getState85283810
initialActions001011
loadScripts13261134157311514191541
setupStore7531378
Benchmark value 20 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 47 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 314 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 41 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 18ms | Sum of p95 exceeds: 251ms
Sum of all benchmark exceeds: 269ms

Bundle size diffs
  • background: 0 Bytes (0%)
  • ui: 531 Bytes (0.01%)
  • common: 0 Bytes (0%)

metamaskbot avatar Jun 13 '25 14:06 metamaskbot

Builds ready [37d5ed6]
UI Startup Metrics (1204 ± 69 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1204109615626912451302
load104795613606310911130
domContentLoaded104193513476210871122
domInteractive17133551630
firstPaint68370116842310531116
backgroundConnect74404710
firstReactRender19164532022
getState1253271726
initialActions001001
loadScripts796701109060840883
setupStore85172813
WebpackHomeuiStartup20451603248421221842415
load15921265197816417021827
domContentLoaded15871261197416416981819
domInteractive161153101348
firstPaint1515937055181244
backgroundConnect2213259242332
firstReactRender13545356106131346
getState942231116
initialActions512842834
loadScripts15841260196416216921809
setupStore3973188316309
FirefoxBrowserifyHomeuiStartup1340117816138413871515
load1191105314388112311348
domContentLoaded1191105214388112311348
domInteractive973620026109139
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect211397122128
firstReactRender23205242427
getState74121810
initialActions001001
loadScripts1172103613937812141336
setupStore64384610
WebpackHomeuiStartup1468130717429315101650
load1278113515619413141465
domContentLoaded1277113415609413141464
domInteractive77421491686103
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20154442125
firstReactRender39294744246
getState95315928
initialActions001011
loadScripts1259111915439512921446
setupStore85606810
Benchmark value 39 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 309 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 40 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 9ms | Sum of p95 exceeds: 244ms
Sum of all benchmark exceeds: 253ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 0 Bytes (0%)
  • ui: 481 Bytes (0.01%)
  • common: 1 Bytes (0%)

metamaskbot avatar Jun 13 '25 15:06 metamaskbot

Builds ready [12a4653]
UI Startup Metrics (1218 ± 63 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1218109813796312611326
load105391011816511011162
domContentLoaded104789911766510961156
domInteractive17133751632
firstPaint69973118143110881163
backgroundConnect6424279
firstReactRender2416155192138
getState1364582029
initialActions001001
loadScripts80366593565856905
setupStore85183914
WebpackHomeuiStartup21431661258420922402528
load16811294206818418021961
domContentLoaded16751290206218417971949
domInteractive161266111351
firstPaint1696459790210283
backgroundConnect21103962435
firstReactRender13746368108118355
getState1153551219
initialActions1113044835
loadScripts16721288206118217951942
setupStore4873399519317
FirefoxBrowserifyHomeuiStartup1362121715837314061503
load1207106614597312461365
domContentLoaded1206106614597312461364
domInteractive1053731233110158
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect221497102242
firstReactRender24215942429
getState74121810
initialActions001001
loadScripts1186105214467412281348
setupStore64223611
WebpackHomeuiStartup1465131117648815021661
load1277113815709013191466
domContentLoaded1277113815709013191466
domInteractive75541281477108
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20164032124
firstReactRender39345634144
getState84325827
initialActions001011
loadScripts1259112415499113021452
setupStore759178
Benchmark value 24 exceeds gate value 23 for chrome browserify home mean firstReactRender
Benchmark value 11 exceeds gate value 7 for chrome webpack home mean initialActions
Benchmark value 48 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 2528 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 317 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 40 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 23ms | Sum of p95 exceeds: 326ms
Sum of all benchmark exceeds: 349ms

Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 0 Bytes (0%)
  • ui: 3.14 KiB (0.04%)
  • common: 1.47 KiB (0.02%)

metamaskbot avatar Jun 13 '25 15:06 metamaskbot