Skip to content

fix(zone.js): setTimeout patch should clean tasksByHandleId cache #40586

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

Closed
wants to merge 1 commit into from

Conversation

JiaLiPassion
Copy link
Contributor

@JiaLiPassion JiaLiPassion commented Jan 27, 2021

Close #40387

Currently zone.js patches setTimeout and keeps a tasksByHandleId map to keep timerId <-> ZoneTask relationship. This is needed so that when clearTimeout(timerId) is called, zone.js can find the associated ZoneTask. Now zone.js set the tasksByHandleId map in the scheduleTask function, but if the setTimeout is running in the FakeAsyncZoneSpec or any other ZoneSpec with onScheduleTask hooks. The scheduleTask in timer patch may not be invoked.

For example:

fakeAsync(() => {
  setTimeout(() => {});
  tick();
});

In this case, the timerId kept in the tasksByHandleId map is not cleared.
This is because the FakeAsyncZoneSpec in the onScheduleTask hook looks like this.

onScheduleTask(delegate, ..., task) {
  fakeAsyncScheduler.setTimeout(task);
  return task;
}

Because FakeAsyncZoneSpec handles the task itself and it doesn't call parentDelegate.onScheduleTask,
therefore the default scheduleTask in the timer patch is not invoked.

In this commit, the cleanup logic is moved from scheduleTask to setTimeout patch entry to
avoid the memory leak.

@JiaLiPassion JiaLiPassion added area: zones Issues related to zone.js target: minor This PR is targeted for the next minor release labels Jan 27, 2021
@JiaLiPassion JiaLiPassion requested a review from mhevery January 27, 2021 02:07
@ngbot ngbot bot modified the milestone: Backlog Jan 27, 2021
@google-cla google-cla bot added the cla: yes label Jan 27, 2021
Copy link
Contributor

@mhevery mhevery left a comment

Choose a reason for hiding this comment

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

  • I have updated the PR text for grammar. Please update the commit message to match.
  • The change seems reasonable, but it needs a test. Could you add one.

@JiaLiPassion
Copy link
Contributor Author

@mhevery , thanks for the review, I have updated the commit message, but since this tasksByHandleId is an internal variable, I am not sure how to verify it by the test case, I have tested it with debugger in the chrome dev tool.

@JiaLiPassion JiaLiPassion requested a review from mhevery January 29, 2021 22:54
@JiaLiPassion JiaLiPassion force-pushed the fakeasync-timer branch 2 times, most recently from 2faa5ad to 098bf71 Compare January 30, 2021 08:57
@mhevery
Copy link
Contributor

mhevery commented Feb 1, 2021

@mhevery , thanks for the review, I have updated the commit message, but since this tasksByHandleId is an internal variable, I am not sure how to verify it by the test case, I have tested it with debugger in the chrome dev tool.

I don't think you need to have tasksByHandleId public to test it. I assume that without this change there is a scenario which miss-behaves. Could we describe that scenario in the test?

@JiaLiPassion
Copy link
Contributor Author

@mhevery , yeah, I understand your point, now most cases behave correctly even the tasksByHandleId is not cleared, I will try to create a failure case.

Close angular#40387

Currently zone.js patches `setTimeout` and keeps a `tasksByHandleId` map to keep `timerId` <-> `ZoneTask`
relationship. This is needed so that when `clearTimeout(timerId)` is called, zone.js can find the associated
`ZoneTask`. Now zone.js set the `tasksByHandleId` map in the `scheduleTask` function, but if the `setTimeout`
is running in the `FakeAsyncZoneSpec` or any other `ZoneSpec` with `onScheduleTask` hooks. The `scheduleTask`
in `timer` patch may not be invoked.

For example:

```
fakeAsync(() => {
  setTimeout(() => {});
  tick();
});
```

In this case, the `timerId` kept in the `tasksByHandleId` map is not cleared.
This is because the `FakeAsyncZoneSpec` in the `onScheduleTask` hook looks like this.

```
onScheduleTask(delegate, ..., task) {
  fakeAsyncScheduler.setTimeout(task);
  return task;
}
```

Because `FakeAsyncZoneSpec` handles the task itself and it doesn't call `parentDelegate.onScheduleTask`,
therefore the default `scheduleTask` in the `timer` patch is not invoked.

In this commit, the cleanup logic is moved from `scheduleTask` to `setTimeout` patch entry to
avoid the memory leak.
@JiaLiPassion
Copy link
Contributor Author

@mhevery , I have added one test case to describe the fix, so this issue will only cause memory leak when using fakeAsync() with setTimeout(), the behavior from user's perspective is totally the same, so I use some hack way to test the internal behavior, please review, thanks.

@mhevery mhevery added the action: merge The PR is ready for merge by the caretaker label Feb 2, 2021
@mhevery
Copy link
Contributor

mhevery commented Feb 2, 2021

presubmit

@mhevery
Copy link
Contributor

mhevery commented Feb 8, 2021

presubmit

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: zones Issues related to zone.js cla: yes target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory leak in tests with zone.js >= 0.9.0
2 participants