Commit b36e637
authored
fix(perps): resolve CLIENT_NOT_INITIALIZED race condition during initialization (MetaMask#22178)
## **Description**
This PR adds initialization state machine and guards to prevent
CLIENT_NOT_INITIALIZED errors in the Perps feature.
**Problem:**
- Users experiencing CLIENT_NOT_INITIALIZED errors (1,251 events
affecting 590+ users)
- Errors occur during controller reinitialization when switching
accounts/networks
- `usePerpsPositionData` hook was attempting operations before
controller completed initialization
**Investigation Process:**
- Sentry traces for CLIENT_NOT_INITIALIZED errors pointed to
`usePerpsPositionData.ts`
- Stack traces showed errors occurring during interval-based data
fetching (REST API polling for historical candles)
- Root cause identified: intervals firing during controller
reinitialization
- Reinitialization gap: `providers.clear()` → create new provider →
200ms delay → `isInitialized = true`
- During this gap, `usePerpsPositionData` hook intervals continued
executing, causing CLIENT_NOT_INITIALIZED errors
**Solution:**
1. **PerpsController State Machine**: Added `InitializationState` enum
tracking (UNINITIALIZED � INITIALIZING � INITIALIZED � FAILED) with
retry logic (3 attempts, exponential backoff: 1s, 2s, 4s)
2. **Initialization Guards**: Added checks in `usePerpsPositionData` to
block operations when `initializationState !== 'initialized'`
3. **WebSocket Ready Wait**: Added 500ms delay before marking controller
as initialized to ensure WebSocket transport is ready
4. **Redux Selector**: Created `selectPerpsInitializationState` for UI
components to check initialization status
**Key Changes:**
- `PerpsController.ts`: State machine, retry logic, initialization
tracking
- `usePerpsPositionData.ts`: Guards on historical data loading, price
subscriptions, and interval setup
- `selectors/perpsController/index.ts`: Selector for initialization
state
- `PerpsConnectionManager.ts`: Updated to use new `init()` method
- `wait.ts`: Utility function for delays
**Note on Impact Validation:**
These changes target the identified reinitialization timing gaps that
cause CLIENT_NOT_INITIALIZED errors. Production monitoring after
deployment is required to validate the full impact on all Sentry issues
listed below. Some issues may have multiple contributing factors beyond
the reinitialization gap.
**Android Fix:**
Fixed dependency array issue that caused double-triggering of effects on
Android by removing redundant `isControllerInitialized` from dependency
arrays (kept only `initializationState` since `isControllerInitialized`
is derived from it).
## **Changelog**
CHANGELOG entry: Fixed Perps initialization errors causing
CLIENT_NOT_INITIALIZED failures during account and network switches
## **Related issues**
Expected to address:
https://consensyssoftware.atlassian.net/browse/TAT-1932
**Sentry Issues Expected to Improve:**
- [METAMASK-MOBILE-4RN7](https://metamask.sentry.io/issues/6255388807/)
- CLIENT_NOT_INITIALIZED (933 events, 348 users) **[HIGH confidence]** -
Stack trace directly matches reinitialization gap
- [METAMASK-MOBILE-4QB2](https://metamask.sentry.io/issues/6248653802/)
- getUserAccount failed (164 events, 164 users) **[MODERATE
confidence]** - Same error type, may have multiple code paths
- [METAMASK-MOBILE-4R5X](https://metamask.sentry.io/issues/6253182536/)
- Missing activeProvider (97 events, 46 users) **[MODERATE confidence]**
- Same error type, may have multiple code paths
- [METAMASK-MOBILE-4RZD](https://metamask.sentry.io/issues/6259562071/)
- fetchHistoricalCandles error (38 events, 31 users) **[MODERATE
confidence]** - Same error type, may have multiple code paths
- [METAMASK-MOBILE-4SW3](https://metamask.sentry.io/issues/6268066927/)
- Empty account address (17 events, 5 users) **[MODERATE confidence]** -
Same error type, may have multiple code paths
- [METAMASK-MOBILE-4TR2](https://metamask.sentry.io/issues/6275226234/)
- Empty orderbook (2 events, 2 users) **[MODERATE confidence]** - Same
error type, may have multiple code paths
**Validation Approach:**
The initialization guards specifically target the ~200ms
reinitialization gap that occurs during account/network switches.
Post-deployment monitoring is essential to:
1. Confirm reduction in CLIENT_NOT_INITIALIZED error rates
2. Identify any remaining edge cases not covered by these changes
3. Validate that the improvements don't introduce new issues
## **Manual testing steps**
```gherkin
Feature: Perps Controller Initialization Guards
Scenario: User opens Perps feature on cold start
Given the app is not running
When user launches the app
And navigates to Perps
Then PerpsLoadingSkeleton should be displayed
And controller should initialize within 3 attempts
And user should see market data without CLIENT_NOT_INITIALIZED errors
Scenario: User switches accounts while viewing Perps
Given user is on Perps Trade screen with Account A
When user switches to Account B via account selector
Then loading state should appear during reinitialization
And chart data should refresh for new account
And no CLIENT_NOT_INITIALIZED errors should occur
Scenario: User switches networks while viewing Perps
Given user is on Perps with mainnet
When user switches to testnet via network selector
Then loading state should appear during reinitialization
And provider should reinitialize with testnet configuration
And no CLIENT_NOT_INITIALIZED errors should occur
Scenario: Initialization fails due to network issues
Given the device has poor network connectivity
When user opens Perps feature
Then controller should retry initialization 3 times with exponential backoff (1s, 2s, 4s)
And appropriate error state should be shown if all attempts fail
Scenario: Android chart rendering
Given user is on Android device
When user navigates to Perps Trade screen
Then chart should render candles without showing skeleton indefinitely
And candles should load correctly on first try
```
## **Screenshots/Recordings**
### **Before**
CLIENT_NOT_INITIALIZED errors occurring during:
- Account switches
- Network changes
- Rapid navigation
- Cold starts with slow connections
See Sentry dashboard for error frequency (1,251 events affecting 590+
users).
### **After**
- Controller initialization tracked via state machine
- Retry logic handles transient failures
- UI operations deferred until initialization complete
- Existing PerpsLoadingSkeleton provides user feedback during
initialization
- Android charts render correctly without dependency array
double-triggering
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/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
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/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]
> Adds an initialization state machine with retry/backoff, gates UI work
until initialized, updates connection manager to use new `init()`, and
introduces a shared `wait` utility with corresponding tests and state
fixtures.
>
> - **PerpsController**:
> - Introduces `InitializationState` state machine with
`initializationState/error/attempts` in Redux.
> - Replaces `initializeProviders()` with `init()`; adds retry with
exponential backoff and `wait` utility; defers `isInitialized` until WS
ready; clearer errors in `getActiveProvider`.
> - Network toggle now re-inits via `init()`; exports
`InitializationState`.
> - **Hooks/UI**:
> - `usePerpsPositionData` defers historical fetch, subscriptions, and
intervals until `initializationState === 'initialized'`.
> - **Selectors**:
> - Adds `selectPerpsInitializationState`.
> - **Connection Manager**:
> - Uses `PerpsController.init()` and shared `wait`;
reconnection/connection flows updated accordingly.
> - **Utilities**:
> - Adds `utils/wait` with tests.
> - **Tests/Fixtures**:
> - Updates tests to new init flow and guards; mocks `wait`; adds init
retry test; updates Engine tests, snapshots, and initial background
state with new initialization fields.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
08f44da. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 955cd1e commit b36e637
15 files changed
Lines changed: 490 additions & 147 deletions
File tree
- app
- components/UI/Perps
- controllers
- hooks
- selectors/perpsController
- services
- utils
- core/Engine
- controllers/perps-controller
- util
- logs/__snapshots__
- test
Lines changed: 139 additions & 57 deletions
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
| 119 | + | |
123 | 120 | | |
124 | 121 | | |
125 | 122 | | |
126 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
127 | 134 | | |
128 | 135 | | |
129 | 136 | | |
| |||
143 | 150 | | |
144 | 151 | | |
145 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
146 | 158 | | |
147 | 159 | | |
148 | 160 | | |
| |||
266 | 278 | | |
267 | 279 | | |
268 | 280 | | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
269 | 284 | | |
270 | 285 | | |
271 | 286 | | |
| |||
345 | 360 | | |
346 | 361 | | |
347 | 362 | | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
348 | 381 | | |
349 | 382 | | |
350 | 383 | | |
| |||
706 | 739 | | |
707 | 740 | | |
708 | 741 | | |
709 | | - | |
710 | | - | |
711 | | - | |
712 | | - | |
713 | 742 | | |
714 | 743 | | |
715 | 744 | | |
| |||
1169 | 1198 | | |
1170 | 1199 | | |
1171 | 1200 | | |
1172 | | - | |
| 1201 | + | |
1173 | 1202 | | |
1174 | 1203 | | |
1175 | 1204 | | |
| |||
1183 | 1212 | | |
1184 | 1213 | | |
1185 | 1214 | | |
1186 | | - | |
| 1215 | + | |
1187 | 1216 | | |
1188 | 1217 | | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
1189 | 1227 | | |
1190 | 1228 | | |
1191 | 1229 | | |
1192 | 1230 | | |
1193 | 1231 | | |
1194 | 1232 | | |
1195 | | - | |
1196 | | - | |
1197 | | - | |
1198 | | - | |
1199 | | - | |
1200 | | - | |
1201 | | - | |
1202 | | - | |
1203 | | - | |
1204 | | - | |
1205 | | - | |
1206 | | - | |
| 1233 | + | |
1207 | 1234 | | |
1208 | | - | |
1209 | | - | |
1210 | | - | |
1211 | | - | |
1212 | | - | |
1213 | | - | |
1214 | | - | |
1215 | | - | |
1216 | | - | |
1217 | | - | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
1218 | 1240 | | |
1219 | | - | |
1220 | | - | |
1221 | | - | |
1222 | | - | |
1223 | | - | |
1224 | | - | |
1225 | | - | |
1226 | | - | |
1227 | | - | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
1228 | 1295 | | |
1229 | | - | |
1230 | | - | |
1231 | | - | |
1232 | | - | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
1233 | 1299 | | |
1234 | | - | |
1235 | | - | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
1236 | 1307 | | |
1237 | | - | |
1238 | | - | |
1239 | | - | |
1240 | | - | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
1241 | 1334 | | |
1242 | 1335 | | |
1243 | 1336 | | |
| |||
1292 | 1385 | | |
1293 | 1386 | | |
1294 | 1387 | | |
1295 | | - | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
1296 | 1397 | | |
1297 | | - | |
| 1398 | + | |
1298 | 1399 | | |
1299 | 1400 | | |
1300 | | - | |
| 1401 | + | |
1301 | 1402 | | |
1302 | 1403 | | |
1303 | 1404 | | |
| |||
3746 | 3847 | | |
3747 | 3848 | | |
3748 | 3849 | | |
3749 | | - | |
| 3850 | + | |
3750 | 3851 | | |
3751 | 3852 | | |
3752 | 3853 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
34 | 43 | | |
35 | 44 | | |
36 | 45 | | |
| |||
0 commit comments