Skip to content

Commit 3257236

Browse files
fix: Properly stop Snaps when clearing state (#3552)
Properly stop Snaps instead of only terminating their execution when clearing state. This properly cleans up stuck requests that are not terminated gracefully.
1 parent 554d5d0 commit 3257236

File tree

3 files changed

+43
-13
lines changed

3 files changed

+43
-13
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"branches": 95.22,
3-
"functions": 98.72,
3+
"functions": 98.49,
44
"lines": 98.88,
55
"statements": 98.71
66
}

packages/snaps-controllers/src/snaps/SnapController.test.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10211,10 +10211,18 @@ describe('SnapController', () => {
1021110211
});
1021210212

1021310213
describe('clearState', () => {
10214-
it('clears the state and terminates running snaps', async () => {
10214+
it('clears the state, terminates running Snaps and cancels pending requests', async () => {
1021510215
const options = getSnapControllerWithEESOptions({
1021610216
state: {
10217-
snaps: getPersistedSnapsState(),
10217+
snaps: getPersistedSnapsState(
10218+
getPersistedSnapObject({
10219+
sourceCode: `
10220+
module.exports.onRpcRequest = () => {
10221+
while(true) {}
10222+
};
10223+
`,
10224+
}),
10225+
),
1021810226
snapStates: {
1021910227
[MOCK_SNAP_ID]: JSON.stringify({ foo: 'bar' }),
1022010228
},
@@ -10231,19 +10239,39 @@ describe('SnapController', () => {
1023110239

1023210240
expect(snapController.has(MOCK_SNAP_ID)).toBe(true);
1023310241

10242+
const requestPromise = snapController.handleRequest({
10243+
snapId: MOCK_SNAP_ID,
10244+
origin: METAMASK_ORIGIN,
10245+
handler: HandlerType.OnRpcRequest,
10246+
request: {
10247+
method: 'foo',
10248+
},
10249+
});
10250+
10251+
await waitForStateChange(messenger);
10252+
10253+
expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true);
10254+
10255+
await new Promise((resolve) => setTimeout(resolve, 100));
10256+
1023410257
await snapController.clearState();
1023510258

1023610259
expect(snapController.has(MOCK_SNAP_ID)).toBe(false);
1023710260

1023810261
expect(callActionSpy).toHaveBeenCalledWith(
10239-
'ExecutionService:terminateAllSnaps',
10262+
'ExecutionService:terminateSnap',
10263+
MOCK_SNAP_ID,
1024010264
);
1024110265

1024210266
expect(callActionSpy).toHaveBeenCalledWith(
1024310267
'PermissionController:revokeAllPermissions',
1024410268
MOCK_SNAP_ID,
1024510269
);
1024610270

10271+
await expect(requestPromise).rejects.toThrow(
10272+
'npm:@metamask/example-snap was stopped and the request was cancelled. This is likely because the Snap crashed.',
10273+
);
10274+
1024710275
expect(snapController.state).toStrictEqual({
1024810276
snaps: {},
1024910277
snapStates: {},
@@ -10290,13 +10318,22 @@ describe('SnapController', () => {
1029010318
expect(snapController.has(MOCK_SNAP_ID)).toBe(true);
1029110319
expect(snapController.has(preinstalledSnapId)).toBe(true);
1029210320

10321+
await snapController.startSnap(MOCK_SNAP_ID);
10322+
await snapController.startSnap(preinstalledSnapId);
10323+
1029310324
await snapController.clearState();
1029410325

1029510326
expect(snapController.has(MOCK_SNAP_ID)).toBe(false);
1029610327
expect(snapController.has(preinstalledSnapId)).toBe(true);
1029710328

1029810329
expect(callActionSpy).toHaveBeenCalledWith(
10299-
'ExecutionService:terminateAllSnaps',
10330+
'ExecutionService:terminateSnap',
10331+
MOCK_SNAP_ID,
10332+
);
10333+
10334+
expect(callActionSpy).toHaveBeenCalledWith(
10335+
'ExecutionService:terminateSnap',
10336+
preinstalledSnapId,
1030010337
);
1030110338

1030210339
expect(callActionSpy).toHaveBeenCalledWith(

packages/snaps-controllers/src/snaps/SnapController.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ import type {
171171
ExecutionServiceEvents,
172172
HandleRpcRequestAction,
173173
SnapErrorJson,
174-
TerminateAllSnapsAction,
175174
TerminateSnapAction,
176175
} from '../services';
177176
import type {
@@ -675,7 +674,6 @@ export type AllowedActions =
675674
| AddApprovalRequest
676675
| HandleRpcRequestAction
677676
| ExecuteSnapAction
678-
| TerminateAllSnapsAction
679677
| TerminateSnapAction
680678
| UpdateCaveat
681679
| UpdateRequestState
@@ -2267,13 +2265,8 @@ export class SnapController extends BaseController<
22672265
*/
22682266
async clearState() {
22692267
const snapIds = Object.keys(this.state.snaps);
2270-
if (this.#closeAllConnections) {
2271-
snapIds.forEach((snapId) => {
2272-
this.#closeAllConnections?.(snapId);
2273-
});
2274-
}
22752268

2276-
await this.messagingSystem.call('ExecutionService:terminateAllSnaps');
2269+
await this.stopAllSnaps();
22772270
snapIds.forEach((snapId) => this.#revokeAllSnapPermissions(snapId));
22782271

22792272
this.update((state) => {

0 commit comments

Comments
 (0)