Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/snaps-controllers/coverage.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"branches": 93.34,
"functions": 97.36,
"functions": 97.37,
"lines": 98.33,
"statements": 98.07
}
49 changes: 29 additions & 20 deletions packages/snaps-controllers/src/snaps/SnapController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ export type SnapRuntimeData = {
* A mutex to prevent concurrent state updates.
*/
stateMutex: Mutex;

/**
* A mutex to prevent concurrent state decryption.
*/
getStateMutex: Mutex;
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if we could re-use the stateMutex

Copy link
Member Author

Choose a reason for hiding this comment

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

It could, but I'm not sure if it should? We can do encryption and decryption at the same time, they don't depend on each other.

};

export type SnapError = {
Expand Down Expand Up @@ -2046,33 +2051,36 @@ export class SnapController extends BaseController<
*/
async getSnapState(snapId: SnapId, encrypted: boolean): Promise<Json> {
const runtime = this.#getRuntimeExpect(snapId);
const cachedState = encrypted ? runtime.state : runtime.unencryptedState;
return await runtime.getStateMutex.runExclusive(async () => {
Copy link
Member

@FrederikBolding FrederikBolding Mar 24, 2025

Choose a reason for hiding this comment

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

With this cached requests would still have to queue to access the cache, right? That seems less than ideal 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it work if we have a cacheMutex that nests this one?

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you mean by nesting in this case?

With this cached requests would still have to queue to access the cache, right? That seems less than ideal 🤔

Yeah, but cached access should be much quicker, so I don't think it's a big issue?

Copy link
Member

Choose a reason for hiding this comment

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

Let's try this as-is and we can always move some logic outside of the mutex block

const cachedState = encrypted ? runtime.state : runtime.unencryptedState;

if (cachedState !== undefined) {
return cachedState;
}
if (cachedState !== undefined) {
return cachedState;
}

const state = encrypted
? this.state.snapStates[snapId]
: this.state.unencryptedSnapStates[snapId];
const state = encrypted
? this.state.snapStates[snapId]
: this.state.unencryptedSnapStates[snapId];

if (state === null || state === undefined) {
return null;
}
if (state === null || state === undefined) {
return null;
}

if (!encrypted) {
// For performance reasons, we do not validate that the state is JSON,
// since we control serialization.
const json = JSON.parse(state);
runtime.unencryptedState = json;
if (!encrypted) {
// For performance reasons, we do not validate that the state is JSON,
// since we control serialization.
const json = JSON.parse(state);
runtime.unencryptedState = json;

return json;
}
return json;
}

const decrypted = await this.#decryptSnapState(snapId, state);
runtime.state = decrypted;
const decrypted = await this.#decryptSnapState(snapId, state);
// eslint-disable-next-line require-atomic-updates
runtime.state = decrypted;

return decrypted;
return decrypted;
});
}

/**
Expand Down Expand Up @@ -3968,6 +3976,7 @@ export class SnapController extends BaseController<
interpreter,
stopping: false,
stateMutex: new Mutex(),
getStateMutex: new Mutex(),
});
}

Expand Down
Loading