Skip to content
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

fix(NODE-5588): recursive calls to next cause memory leak #3841

Merged
merged 2 commits into from Aug 29, 2023

Conversation

nbbeeken
Copy link
Contributor

@nbbeeken nbbeeken commented Aug 23, 2023

Description

What is changing?

  • Modified the isDead condition (moved it to a getter)
    • If the cursorId is zero, if it has been closed or has been killed, it's "dead"
    • if kId is nullish, it has not been initialized yet, a not-even-started-yet cursor is not dead
    • (this[kId]?.isZero() ?? false) || this[kClosed] || this[kKilled];
Is there new documentation needed for these changes?

No

Release Highlight

Fix memory leak with ChangeStreams

In a previous release, 5.7.0, we refactored cursor internals from callbacks to async/await. In particular, the next function that powers cursors was written with callbacks and would recursively call itself depending on the cursor type. For ChangeStreams, this function would call itself if there were no new changes to return to the user. After converting that code to async/await each recursive call created a new promise that saved the current async context. This would slowly build up memory usage if no new changes came in to unwind the recursive calls.

The function is now implemented as a loop, memory leak be gone!

Double check the following

  • Ran npm run check:lint script
  • Self-review completed using the steps outlined here
  • PR title follows the correct format: type(NODE-xxxx)[!]: description
    • Example: feat(NODE-1234)!: rewriting everything in coffeescript
  • Changes are covered by tests
  • New TODOs have a related JIRA ticket

@nbbeeken nbbeeken changed the title fix(NODE-5502): recursive calls to next cause memory leak fix(NODE-5588): recursive calls to next cause memory leak Aug 25, 2023
@nbbeeken nbbeeken marked this pull request as ready for review August 25, 2023 21:30
@@ -220,6 +220,11 @@ export abstract class AbstractCursor<
return this[kId] ?? undefined;
}

/** @internal */
get isDead() {
Copy link
Member

Choose a reason for hiding this comment

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

This makes much more sense to me to be a property of the cursor itself instead of passing a cursor to a function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Did we intentionally change the semantics of isDead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nevermind, I see that now in the description. But I'm still unsure why the logic was changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to exit the loop and enter clean-up logic at the correct times. The prior logic didn't account for a cursor already being closed, or killed, so the loop would attempt a getMore on a killed cursor id. This same "exit" condition applies to all the places the cursorIsDead helper was being used.

@durran durran self-assigned this Aug 28, 2023
@durran durran added the Primary Review In Review with primary reviewer, not yet ready for team's eyes label Aug 28, 2023
Copy link
Member

@durran durran left a comment

Choose a reason for hiding this comment

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

Heap looks good, using bind cleans it up nicely. Well done.

@durran durran added Team Review Needs review from team and removed Primary Review In Review with primary reviewer, not yet ready for team's eyes labels Aug 29, 2023
@nbbeeken
Copy link
Contributor Author

using bind cleans it up nicely.

Thanks! I'd like us to pull back in #3804 after this is in to make them actually async functions

@baileympearson baileympearson self-requested a review August 29, 2023 16:39
Copy link
Contributor

@baileympearson baileympearson left a comment

Choose a reason for hiding this comment

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

two non-blocking questions, otherwise LGTM.

@@ -101,7 +101,9 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize;

if (batchSize <= 0) {
this.close().finally(() => callback());
this.close().finally(() =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this change just for consistency in return from _getMore or is there a reason it's needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, it is for result consistency, I may be able to revert it if I make sure the logic is nullish safe in next but the contract should have been either throw or result and when we make _getMore actually async that will be the case for the promise's resolution type.

This code path also specifically claims it is for legacy servers which we don't support, I'm fairly certain this is only triggered by mock server tests.

@@ -220,6 +220,11 @@ export abstract class AbstractCursor<
return this[kId] ?? undefined;
}

/** @internal */
get isDead() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we intentionally change the semantics of isDead?

@durran durran merged commit 9a8fdb2 into main Aug 29, 2023
18 of 26 checks passed
@durran durran deleted the NODE-5502-next-loop branch August 29, 2023 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team Review Needs review from team
Projects
None yet
3 participants