Skip to content

[Proposal] Batch bg state updates in Engine service #15189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
72 changes: 67 additions & 5 deletions app/core/EngineService/EngineService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import NavigationService from '../NavigationService';
import Routes from '../../constants/navigation/Routes';
import { KeyringControllerState } from '@metamask/keyring-controller';
import { MetaMetrics } from '../Analytics';
import { unstable_batchedUpdates as batchFunc } from 'react-native';

const LOG_TAG = 'EngineService';

Expand All @@ -25,9 +26,73 @@ interface InitializeEngineResult {

const UPDATE_BG_STATE_KEY = 'UPDATE_BG_STATE';
const INIT_BG_STATE_KEY = 'INIT_BG_STATE';

/**
* Batcher class for handling batched operations
* manages a set of items that are processed together after a specified delay
* might be used for other services as well
*/
class Batcher<T> {
// Set of unique pending items to batch
private pending: Set<T> = new Set<T>();
// timer that will trigger the flush()
private timer: NodeJS.Timeout | null = null;
// the function to call when we flush the batch
private handler: (items: T[]) => void;
// time in ms to wait after the first add() before flushing
private delay: number;

constructor(handler: (items: T[]) => void, delay = 0) {
this.handler = handler;
this.delay = delay;
}

// add an item to the pending set and schedule a flush if not already pending.
add(item: T) {
this.pending.add(item);
// if no timer is running, start one
if (this.timer === null) {
this.timer = setTimeout(() => this.flush(), this.delay);
}
}

// clear the timer, empty the set, and invoke the handler with all pending items
flush() {
// cancel the pending timer so we don't call flush twice
if (this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
// snapshot the items and clear for the next batch
const items = Array.from(this.pending);
this.pending.clear();

this.handler(items);
}
}

export class EngineService {
private engineInitialized = false;

private updateBatcher = new Batcher<string>(
(keys) =>
batchFunc(() => {
keys.forEach((key) => {
if (key === INIT_BG_STATE_KEY) {
// first-time init action
ReduxService.store.dispatch({ type: INIT_BG_STATE_KEY });
} else {
// incremental update action
ReduxService.store.dispatch({
type: UPDATE_BG_STATE_KEY,
payload: { key },
});
}
});
}),
0, // delay of 0 means "flush on the next macrotask", might be tuned to higher values
);

/**
* Starts the Engine and subscribes to the controller state changes
*
Expand Down Expand Up @@ -97,7 +162,7 @@ export class EngineService {
if (!engine.context.KeyringController.metadata.vault) {
Logger.log('keyringController vault missing for INIT_BG_STATE_KEY');
}
ReduxService.store.dispatch({ type: INIT_BG_STATE_KEY });
this.updateBatcher.add(INIT_BG_STATE_KEY);
this.engineInitialized = true;
},
() => !this.engineInitialized,
Expand All @@ -107,10 +172,7 @@ export class EngineService {
if (!engine.context.KeyringController.metadata.vault) {
Logger.log('keyringController vault missing for UPDATE_BG_STATE_KEY');
}
ReduxService.store.dispatch({
type: UPDATE_BG_STATE_KEY,
payload: { key: controllerName },
});
this.updateBatcher.add(controllerName);
};

BACKGROUND_STATE_CHANGE_EVENT_NAMES.forEach((eventName) => {
Expand Down
Loading