Skip to content

fix: Fix StaticIntervalPollingController not properly stopping polling if _executePoll was still pending#4230

Merged
adonesky1 merged 17 commits into
mainfrom
jl/fix-static-interval-polling-timeout
May 7, 2024
Merged

fix: Fix StaticIntervalPollingController not properly stopping polling if _executePoll was still pending#4230
adonesky1 merged 17 commits into
mainfrom
jl/fix-static-interval-polling-timeout

Conversation

@jiexi
Copy link
Copy Markdown
Member

@jiexi jiexi commented Apr 29, 2024

Explanation

Currently, there is a bug in the StaticIntervalPollingController that causes polling to not be properly stopped if a stop is requested while _executePoll has not yet resolved for the current loop. This PR adds a guard to check if the id returned by setTimeout still matches what is in state, indicating that polling is still active.

References

Changelog

@metamask/polling-controller

  • FIXED: StaticIntervalPollingControllerOnly, StaticIntervalPollingController, and StaticIntervalPollingControllerV1 now properly stops polling when a stop is requested while _executePoll has not yet resolved for the current loop

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate

Comment thread packages/polling-controller/src/StaticIntervalPollingController.test.ts Outdated
@jiexi jiexi changed the title Jl/fix static interval polling timeout fix: Fix StaticIntervalPollingController not properly stopping polling if _executePoll was still pending Apr 30, 2024
@jiexi jiexi marked this pull request as ready for review April 30, 2024 21:14
@jiexi jiexi requested a review from a team April 30, 2024 21:14
@jiexi
Copy link
Copy Markdown
Member Author

jiexi commented Apr 30, 2024

@metamaskbot publish-preview

@github-actions
Copy link
Copy Markdown
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "13.0.0-preview-02d970ca",
  "@metamask-previews/address-book-controller": "4.0.1-preview-02d970ca",
  "@metamask-previews/announcement-controller": "6.1.0-preview-02d970ca",
  "@metamask-previews/approval-controller": "6.0.1-preview-02d970ca",
  "@metamask-previews/assets-controllers": "28.0.0-preview-02d970ca",
  "@metamask-previews/base-controller": "5.0.2-preview-02d970ca",
  "@metamask-previews/build-utils": "2.0.1-preview-02d970ca",
  "@metamask-previews/composable-controller": "6.0.1-preview-02d970ca",
  "@metamask-previews/controller-utils": "9.1.0-preview-02d970ca",
  "@metamask-previews/ens-controller": "10.0.1-preview-02d970ca",
  "@metamask-previews/eth-json-rpc-provider": "3.0.1-preview-02d970ca",
  "@metamask-previews/gas-fee-controller": "15.1.0-preview-02d970ca",
  "@metamask-previews/json-rpc-engine": "8.0.1-preview-02d970ca",
  "@metamask-previews/json-rpc-middleware-stream": "7.0.1-preview-02d970ca",
  "@metamask-previews/keyring-controller": "15.0.0-preview-02d970ca",
  "@metamask-previews/logging-controller": "3.0.1-preview-02d970ca",
  "@metamask-previews/message-manager": "8.0.1-preview-02d970ca",
  "@metamask-previews/name-controller": "6.0.1-preview-02d970ca",
  "@metamask-previews/network-controller": "18.1.0-preview-02d970ca",
  "@metamask-previews/notification-controller": "5.0.1-preview-02d970ca",
  "@metamask-previews/permission-controller": "9.0.2-preview-02d970ca",
  "@metamask-previews/permission-log-controller": "2.0.1-preview-02d970ca",
  "@metamask-previews/phishing-controller": "9.0.2-preview-02d970ca",
  "@metamask-previews/polling-controller": "6.0.1-preview-02d970ca",
  "@metamask-previews/preferences-controller": "10.0.0-preview-02d970ca",
  "@metamask-previews/queued-request-controller": "0.9.0-preview-02d970ca",
  "@metamask-previews/rate-limit-controller": "5.0.1-preview-02d970ca",
  "@metamask-previews/selected-network-controller": "12.0.1-preview-02d970ca",
  "@metamask-previews/signature-controller": "15.0.0-preview-02d970ca",
  "@metamask-previews/transaction-controller": "28.1.1-preview-02d970ca",
  "@metamask-previews/user-operation-controller": "8.0.1-preview-02d970ca"
}

@jiexi
Copy link
Copy Markdown
Member Author

jiexi commented Apr 30, 2024

Comment thread packages/polling-controller/src/StaticIntervalPollingController.ts Outdated
@legobeat
Copy link
Copy Markdown
Contributor

legobeat commented May 1, 2024

@metamask/assets-controllers tests are failing.

Details
  ● TokenRatesController › polling by networkClientId › should stop polling

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

      1577 |
      1578 |       await advanceTime({ clock, duration: 0 });
    > 1579 |       expect(tokenPricesService.fetchTokenPrices).toHaveBeenCalledTimes(1);
           |                                                   ^
      1580 |
      1581 |       await advanceTime({ clock, duration: interval });
      1582 |       expect(tokenPricesService.fetchTokenPrices).toHaveBeenCalledTimes(1);

      at Object.<anonymous> (src/TokenRatesController.test.ts:1579:51)


  ● AccountTrackerController › should call refresh every interval for each networkClientId being polled

    expect(jest.fn()).toHaveBeenNthCalledWith(n, ...expected)

    n: 5
    Expected: "networkClientId2"
    Received
           3: "networkClientId2"

    Number of calls: 4

      557 |     await advanceTime({ clock, duration: 100 });
      558 |     expect(refreshSpy).toHaveBeenNthCalledWith(4, 'networkClientId1');
    > 559 |     expect(refreshSpy).toHaveBeenNthCalledWith(5, 'networkClientId2');
          |                        ^
      560 |     expect(refreshSpy).toHaveBeenCalledTimes(5);
      561 |
      562 |     controller.stopAllPolling();

      at Object.<anonymous> (src/AccountTrackerController.test.ts:559:24)


  ● CurrencyRateController › should not poll after being stopped

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

      201 |
      202 |     // called once upon initial start
    > 203 |     expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1);
          |                                   ^
      204 |
      205 |     await advanceTime({ clock, duration: 150, stepSize: 50 });
      206 |

      at Object.<anonymous> (src/CurrencyRateController.test.ts:203:35)

  ● CurrencyRateController › should poll correctly after being started, stopped, and started again

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

      225 |
      226 |     // called once upon initial start
    > 227 |     expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1);
          |                                   ^
      228 |
      229 |     controller.startPollingByNetworkClientId('sepolia');
      230 |     await advanceTime({ clock, duration: 0 });

      at Object.<anonymous> (src/CurrencyRateController.test.ts:227:35)

------------------------------------|---------|----------|---------|---------|-----------------------------
File                                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s           
------------------------------------|---------|----------|---------|---------|-----------------------------
All files                           |    97.7 |    91.05 |   97.03 |   97.68 |                             
 src                                |   98.44 |    91.98 |   98.26 |   98.42 |                             
  AccountTrackerController.ts       |   96.62 |    92.85 |     100 |   96.62 | 289-290,335                 
  AssetsContractController.ts       |     100 |      100 |     100 |     100 |                             
  CurrencyRateController.ts         |     100 |      100 |     100 |     100 |                             
  NftController.ts                  |   99.66 |    87.63 |     100 |   99.66 | 1706                        
  NftDetectionController.ts         |   98.52 |    92.59 |   94.44 |    98.5 | 498                         
  TokenBalancesController.ts        |     100 |      100 |     100 |     100 |                             
  TokenDetectionController.ts       |   94.89 |    83.72 |   89.28 |   94.81 | 60-63,426,489,529,543       
  TokenListController.ts            |     100 |      100 |     100 |     100 |                             
  TokenRatesController.ts           |   96.29 |    86.11 |     100 |   96.26 | 126-132,577                 
  TokensController.ts               |   97.94 |    96.69 |   97.43 |   97.93 | 589,713,734-736             
  assetsUtil.ts                     |     100 |      100 |     100 |     100 |                             
  constants.ts                      |     100 |      100 |     100 |     100 |                             
  crypto-compare.ts                 |     100 |      100 |     100 |     100 |                             
  token-service.ts                  |     100 |      100 |     100 |     100 |                             
 src/Standards                      |      95 |       80 |     100 |      95 |                             
  ERC20Standard.ts                  |      95 |       80 |     100 |      95 | 72,109                      
 src/Standards/NftStandards/ERC1155 |   73.17 |    14.28 |   82.35 |   73.17 |                             
  ERC1155Standard.ts                |   73.17 |    14.28 |   82.35 |   73.17 | 122-126,219,229-230,239-244 
 src/Standards/NftStandards/ERC721  |   98.11 |     90.9 |     100 |   98.11 |                             
  ERC721Standard.ts                 |   98.11 |     90.9 |     100 |   98.11 | 207                         
 src/token-prices-service           |     100 |      100 |      80 |     100 |                             
  codefi-v2.ts                      |     100 |      100 |     100 |     100 |                             
  index.ts                          |     100 |      100 |       0 |     100 |                             
------------------------------------|---------|----------|---------|---------|----------------------------

});

it('should start and stop polling sessions for different networkClientIds with the same options', async () => {
controller.setIntervalLength(TICK_TIME);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pretty minor but was this always extraneous?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah, this is set in the beforeEach already

Comment on lines +277 to +288
it('should stop polling session after current iteration if stop is requested while current iteration is still executing', async () => {
const pollingToken = controller.startPollingByNetworkClientId('mainnet');
await advanceTime({ clock, duration: 0 });
expect(controller._executePoll).toHaveBeenCalledTimes(1);
controller.stopPollingByPollingToken(pollingToken);
controller.executePollPromises[0].resolve();
await advanceTime({ clock, duration: TICK_TIME });
expect(controller._executePoll).toHaveBeenCalledTimes(1);
await advanceTime({ clock, duration: TICK_TIME });
expect(controller._executePoll).toHaveBeenCalledTimes(1);
controller.stopAllPolling();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not blocking but should we do a version of this test where we have multiple polling keys running?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

not sure if necessary since we have other tests that specifically test that stopping polling for one key does not stop polling for other keys

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair yeah I guess I'm wondering about a test like starting polling sessions for a few keys and then calling stopAllPolling before execution resolves for both.

adonesky1
adonesky1 previously approved these changes May 1, 2024
Copy link
Copy Markdown
Contributor

@adonesky1 adonesky1 left a comment

Choose a reason for hiding this comment

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

LGTM! Just one non-blocking request for an additional test

@jiexi jiexi requested a review from matthewwalsh0 May 1, 2024 20:46
Comment thread packages/polling-controller/src/StaticIntervalPollingController.ts
@adonesky1 adonesky1 merged commit cd189f1 into main May 7, 2024
@adonesky1 adonesky1 deleted the jl/fix-static-interval-polling-timeout branch May 7, 2024 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants