Skip to content

Abstract domain provider from its stream transport#6670

Merged
danfinlay merged 12 commits intodevelopfrom
AbstractStreamFromProvider
Jul 15, 2019
Merged

Abstract domain provider from its stream transport#6670
danfinlay merged 12 commits intodevelopfrom
AbstractStreamFromProvider

Conversation

@danfinlay
Copy link
Copy Markdown
Contributor

@danfinlay danfinlay commented May 30, 2019

Creating new provider-consuming extensions, like a new platform or creating an internal resource that needs the ability to suggest signatures and transactions can be frustrating for new contributors because our provider construction has been tangled with our streaming interface.

Here I've broken up our streaming domain connection from the provider construction, so developers can more easily construct local and domain-restricted providers without dealing with streams.

Creating new provider-consuming extensions, like [a new
platform](https://github.com/MetaMask/metamask-extension/blob/develop/docs/porting_to_new_environment.md)
can be frustrating for new contributors because our provider
construction has been tangled with our streaming interface.

Here I've broken up our streaming domain connection from the provider
construction, so developers can more easily construct local and
domain-restricted providers without dealing with streams.
@danfinlay
Copy link
Copy Markdown
Contributor Author

Ideally this would be extended to also break apart the publicApi from its streaming abstraction, but that doesn't need to block this first part.

@danfinlay danfinlay force-pushed the AbstractStreamFromProvider branch from 5333c1c to 777e062 Compare May 30, 2019 19:31
@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [bd0a52a]: chrome, firefox, edge, opera

Copy link
Copy Markdown
Contributor

@whymarrh whymarrh left a comment

Choose a reason for hiding this comment

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

This seems like a good straightforward refactoring

@danfinlay danfinlay merged commit 78cdee2 into develop Jul 15, 2019
@danfinlay danfinlay deleted the AbstractStreamFromProvider branch July 15, 2019 23:28
github-merge-queue bot pushed a commit that referenced this pull request Mar 11, 2026
…#39309)

## **Description**

`ShieldSubscriptionProvider` was creating a new context value object on
every render and recreating `evaluateCohortEligibility` whenever any of
its 10 dependencies changed, causing unnecessary re-renders of the Home
component subtree.

Two fixes applied:

1. **Stabilized callback with ref pattern** -- Moved the
`evaluateCohortEligibility` implementation into a ref updated via
`useEffect`. The public callback delegates to the ref, keeping a stable
reference (`useCallback` with empty deps) while still accessing current
closure values.

2. **Memoized context value** -- Wrapped the provider value object in
`useMemo` keyed on the now-stable callback, preventing a new object
reference on every render.

This is fix 6 of 6 in the [P0 Global Cascade Re-render Fixes
epic](https://github.com/MetaMask/MetaMask-planning/issues/6669)
([RCA](https://docs.google.com/document/d/1KrzqL1IEQb-ctOKpOxhjBVr_-5jDXTgwC8a4r1ICBg0/edit?tab=t.5g1bh4tznel0),
[Profiling
Report](https://docs.google.com/document/d/1zlTg8rWYUFKZAsjQF3I9o7KaMA6uq7qcYjdfWKIZIJU/edit?tab=t.0#heading=h.dxmx8vunwu2s)).
Together, the 6 memoization fixes break the cascade chain originating in
Routes/Home that propagated through the entire component tree on every
navigation and state update.

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

## **Profiling Results** (all 6 fixes combined -- [Full
Report](https://docs.google.com/document/d/1zlTg8rWYUFKZAsjQF3I9o7KaMA6uq7qcYjdfWKIZIJU/edit?tab=t.0#heading=h.dxmx8vunwu2s))

We ran 7 rounds of controlled A/B benchmarks over 41 hours. Results
confirm the cascade was the dominant source of interactive UI latency.
WDYR audit: 0 cascade re-renders on treatment (RCA measured 125-200 per
action on baseline).

> Measured on test builds (`yarn build:test`) with React StrictMode,
Power User persona (30 accounts). All p-values from Welch's t-test,
confirmed by Mann-Whitney U. Effect sizes: Cohen's d = 0.93-5.11 (large
to very large). n=5-20 per metric.

### Per-interaction improvements

| Flow | Baseline | Treatment | Improvement | p |
|------|----------|-----------|-------------|---|
| Account switching (5 accts) | 3.1s | 1.7s | **-1.4s, 47% faster** |
0.044 |
| Asset detail nav (ETH) | 83ms | 37ms | **-46ms, 55% faster** | 0.044 |
| Asset detail nav (SOL) | 75ms | 31ms | **-44ms, 59% faster** | <0.001
|
| Send flow (e2e) | 928ms | 804ms | -124ms, 13% faster | 0.044 |
| Send: token select to form | 25ms | 13ms | **-12ms, 49% faster** |
0.040 |

### Cascade compounding under sustained interaction

Route cycling benchmark (3 round-trips: Send -> Assets -> Swap):

| Segment | Baseline | Treatment | Improvement | p |
|---------|----------|-----------|-------------|---|
| **Total (6 transitions)** | **19.9s** | **2.7s** | **-17.2s, 7x
faster** | 0.004 |
| sendToHome | 6.4s | 723ms | -5.7s, 89% faster | 0.018 |
| assetDetailsToHome | 6.3s | 659ms | -5.7s, 90% faster | 0.004 |
| swapToHome | 5.0s | 787ms | -4.2s, 84% faster | 0.001 |
| homeToSwap | 877ms | 227ms | -650ms, 74% faster | 0.002 |
| homeToAssetDetails | 628ms | 236ms | -392ms, 62% faster | 0.004 |
| homeToSend | 673ms | 82ms | -591ms, 88% faster | 0.046 |

The 7x is a **compounding effect** across successive transitions, not a
per-transition effect. Isolated transitions show only 10-15%
improvement. Each navigation accumulates subscription/listener state on
baseline, escalating re-render overhead -- consistent with the RCA's
cascade chain model (Routes -> Home -> all children).

This compounding behavior is itself a key finding: it means any
remaining or future cascade leak will degrade **non-linearly** with
session length, even if it looks trivial in a single benchmark.

**Production estimate:** StrictMode roughly doubles render work. The 7x
measured in dev builds is expected to land at ~2-7x in production
depending on wallet size, tree depth, and GC timing.

### Metrics to watch after merge

- **CI (`metamask-performance`):** `assetClickToPriceChart` should show
a step-change (83->37ms dev). `cascadeFixProfiling` preset can be added
for ongoing regression tracking.
- **Production Sentry (RUM):** Best signal at **p75/p95 navigation
latency** and **INP p95**, segmented by users with 10+ accounts. Expect
improvement on route transitions, account switching, token list
rendering. Signal will be diffuse due to network variance and device
heterogeneity -- don't expect a clean step-function.
- **TBT:** Cascade generates clusters of consecutive long tasks (>50ms)
on every state change. Fix eliminates these. Should show up in Sentry
performance dashboard once long-task tracking is enabled.

## **Changelog**

CHANGELOG entry: Fix memoization issue in top-level context provider
that was causing cascading re-renders.

## **Related issues**

Fixes: MetaMask/MetaMask-planning#6638

Part of epic: [MetaMask-planning#6669 -- P0 Global Cascade Re-render
Fixes](https://github.com/MetaMask/MetaMask-planning/issues/6669)

Sibling PRs:
- [#39310](#39310) --
Memoize MetaMetrics context
([#6640](https://github.com/MetaMask/MetaMask-planning/issues/6640))
- [#39311](#39311) --
Stabilize `withRouterHooks` params/location
([#6671](https://github.com/MetaMask/MetaMask-planning/issues/6671))
- [#39312](#39312) --
Fix `getPendingApprovals` selector
([#6673](https://github.com/MetaMask/MetaMask-planning/issues/6673))
- [#39313](#39313) --
Fix `pendingConfirmationsSortedSelector`
([#6674](https://github.com/MetaMask/MetaMask-planning/issues/6674))
- [#39314](#39314) --
Remove dead What's New modal code
([#6670](https://github.com/MetaMask/MetaMask-planning/issues/6670))

## **Manual testing steps**

## **Screenshots/Recordings**

## **Pre-merge author checklist**

- [x] 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).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] 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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes how `evaluateCohortEligibility` is wired (ref-backed stable
callback) and how the context value is memoized, which could alter
when/with-what state eligibility checks run if the ref is not kept in
sync. Added tests reduce risk but this touches subscription/cohort
gating that affects modal display behavior.
> 
> **Overview**
> Reduces cascading re-renders from `ShieldSubscriptionProvider` by
making the exported `evaluateCohortEligibility` function
reference-stable and memoizing the context `value` object.
> 
> Refactors `evaluateCohortEligibility` to delegate through a
`useRef`-stored implementation (updated during render so it’s available
in commit-phase callbacks) while keeping the public callback
`useCallback([])`. Adds a new Jest test suite validating child
rendering, stable callback/context identity across re-renders, and that
the stable callback still observes updated selector values.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
50a2fed. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Jongsun Suh <jongsun.suh@icloud.com>
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.

3 participants