Skip to content

Commit f636954

Browse files
fix: incoming transaction polling race condition (#6913)
## Explanation Prevent multiple simultaneous timeouts being created if `stop` and `start` are called while a previous asynchronous update is still in progress. ## References ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Prevent duplicate timers and concurrent updates in `IncomingTransactionHelper` by tracking in-progress updates and clearing timeouts, with tests and changelog updated. > > - **Transaction polling (Incoming)**: > - Update `IncomingTransactionHelper` to track in-progress updates with `#isUpdating`, preventing additional intervals from being queued while an update is running. > - Clear any existing timeout before scheduling the next interval to avoid duplicate timers. > - **Tests**: > - Add test ensuring repeated `start`/`stop` calls during an ongoing update only schedule one timeout and perform a single `fetchTransactions` call. > - **Changelog**: > - Add Unreleased fix entry: prevent race condition causing excessive incoming transaction polling. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4cf12fe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 68fa9b6 commit f636954

File tree

3 files changed

+44
-0
lines changed

3 files changed

+44
-0
lines changed

packages/transaction-controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Prevent race condition causing excessive incoming transaction polling ([#6913](https://github.com/MetaMask/core/pull/6913))
13+
1014
## [60.9.0]
1115

1216
### Added

packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,31 @@ describe('IncomingTransactionHelper', () => {
376376

377377
expect(jest.getTimerCount()).toBe(0);
378378
});
379+
380+
it('does not queue additional updates if first is still running', async () => {
381+
const remoteTransactionSource = createRemoteTransactionSourceMock([]);
382+
383+
const helper = new IncomingTransactionHelper({
384+
...CONTROLLER_ARGS_MOCK,
385+
remoteTransactionSource,
386+
});
387+
388+
helper.start();
389+
helper.stop();
390+
391+
helper.start();
392+
helper.stop();
393+
394+
helper.start();
395+
396+
await flushPromises();
397+
398+
expect(jest.getTimerCount()).toBe(1);
399+
400+
expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledTimes(
401+
1,
402+
);
403+
});
379404
});
380405

381406
describe('stop', () => {

packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export class IncomingTransactionHelper {
4545

4646
#isRunning: boolean;
4747

48+
#isUpdating: boolean;
49+
4850
readonly #messenger: TransactionControllerMessenger;
4951

5052
readonly #remoteTransactionSource: RemoteTransactionSource;
@@ -88,6 +90,7 @@ export class IncomingTransactionHelper {
8890
this.#includeTokenTransfers = includeTokenTransfers;
8991
this.#isEnabled = isEnabled ?? (() => true);
9092
this.#isRunning = false;
93+
this.#isUpdating = false;
9194
this.#messenger = messenger;
9295
this.#remoteTransactionSource = remoteTransactionSource;
9396
this.#trimTransactions = trimTransactions;
@@ -109,6 +112,10 @@ export class IncomingTransactionHelper {
109112

110113
this.#isRunning = true;
111114

115+
if (this.#isUpdating) {
116+
return;
117+
}
118+
112119
this.#onInterval().catch((error) => {
113120
log('Initial polling failed', error);
114121
});
@@ -129,13 +136,21 @@ export class IncomingTransactionHelper {
129136
}
130137

131138
async #onInterval() {
139+
this.#isUpdating = true;
140+
132141
try {
133142
await this.update({ isInterval: true });
134143
} catch (error) {
135144
console.error('Error while checking incoming transactions', error);
136145
}
137146

147+
this.#isUpdating = false;
148+
138149
if (this.#isRunning) {
150+
if (this.#timeoutId) {
151+
clearTimeout(this.#timeoutId as number);
152+
}
153+
139154
this.#timeoutId = setTimeout(
140155
// eslint-disable-next-line @typescript-eslint/no-misused-promises
141156
() => this.#onInterval(),

0 commit comments

Comments
 (0)