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

refactor(NODE-5915): refactor topology close logic to be synchronous #4021

Merged
merged 18 commits into from Mar 11, 2024

Conversation

W-A-James
Copy link
Contributor

@W-A-James W-A-James commented Mar 5, 2024

Description

What is changing?

  • Refactored the following functions/methods to be synchronous:
    • Connection.destroy
    • ConnectionPool.close
    • Server.destroy
    • Topology.close
  • Removed DestroyOptions as the force flag is unused in all the places it would have been passed in
  • Reordered connection check in and call to Connection.onError in ConnectionPool.interruptInUseConnections due to failure on this test
  • Updated relevant tests to accommodate newly synchronous functions
Is there new documentation needed for these changes?

No

What is the motivation for this change?

NODE-5915

Release Highlight

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

@W-A-James W-A-James marked this pull request as ready for review March 7, 2024 21:52
connection.onError(new PoolClearedOnNetworkError(this));
this.checkIn(connection);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@baileympearson this was done to fix a test failure where we were getting an uncaught error when running this spec test

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is right. Good catch.

I think the reason this worked in the past was because

  1. checkIn calls destroyConnection(), which calls connection.destroy() in a process.nextTick()
  2. then `connection.onError() runs and shuts down the connection
  3. then connection.destroy() is a no-op because the connection has already been destroyed

You made destroyConnection synchronous, so we need to run onError first to ensure the connection is closed with the correct error. Then we run checkIn, which resets the pool state and calls connection.destroy(), which is a no-op.

@aditi-khare-mongoDB aditi-khare-mongoDB added the Primary Review In Review with primary reviewer, not yet ready for team's eyes label Mar 8, 2024
Copy link
Contributor

@aditi-khare-mongoDB aditi-khare-mongoDB left a comment

Choose a reason for hiding this comment

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

left a few small comments, otherwise LGTM, nice work :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's file a follow-up ticket in the V7 epic to remove closeOptions since its no longer used anywhere in the code base after these changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good callout here. I can file the follow-up and the first subtask of that follow-up can be to mark it as deprecated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking a little closer, it seems the only places that interface was used are in Topology.close and ConnectionPool.close which are both internal classes, so users shouldn't be interacting with this interface at all. Seems like it wouldn't be a breaking change to just remove it in this PR along with DestroyOptions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, even though it's only used in internal classes / types, it's public and exported so technically it's a part of our API. In situations like this in the past, we've considered it breaking to remove and done it in a major release

@@ -28,7 +28,6 @@ import { CancellationToken, TypedEventEmitter } from '../mongo_types';
import type { Server } from '../sdam/server';
import {
type Callback,
eachAsync,
Copy link
Contributor

Choose a reason for hiding this comment

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

The eachAsync utils function is now also used nowhere in the codebase, do we want to remove it? It uses callbacks, and IIUC we no longer want to add any new callback code to the driver.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I agree that removing it is likely the way we want to go here. @nbbeeken @baileympearson thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

Go for it

@W-A-James W-A-James force-pushed the NODE-5915/refactor-topology-close-to-be-synchronous branch from c533616 to b9ee91c Compare March 8, 2024 21:08
Copy link
Contributor

@aditi-khare-mongoDB aditi-khare-mongoDB left a comment

Choose a reason for hiding this comment

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

code changes LGTM! I can't seem to find the follow-up ticket, can you link the follow-up link on this ticket.

@aditi-khare-mongoDB aditi-khare-mongoDB added Team Review Needs review from team and removed Primary Review In Review with primary reviewer, not yet ready for team's eyes labels Mar 8, 2024
@W-A-James
Copy link
Contributor Author

Follow-up ticket: NODE-6007

Comment on lines +617 to +622
topology.close();

const { encrypter } = this[kOptions];
if (encrypter) {
await encrypter.close(this, force);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

can close() throw? If so, both the new and old implementations potentially would leave the encrypter open if topology.close() throws

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the investigation into whether these can throw or not.

doesn't have to be in this PR but do you think it might be worth handling errors anyways, since we could introduce errors into close() in the future?

Comment on lines +249 to +251
this.pool.close();
stateTransition(this, STATE_CLOSED);
this.emit('closed');
Copy link
Contributor

Choose a reason for hiding this comment

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

same question as with client.close() - can pool.close() throw?

@@ -469,7 +469,8 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
selectServerOptions,
(err, server) => {
if (err) {
return this.close({ force: false }, () => exitWithError(err));
this.close();
Copy link
Contributor

Choose a reason for hiding this comment

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

same question about errors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

method relevant direct dependencies throws?
Connection.destroy - No
ConnectionPool.close Connection.destroy No
Server.destroy stateTransition*, ConnectionPool.close No; stateTransition* can technically throw, but Server.destroy is written such that it will never try to make an illegal state transition.
destroyServer Server.destroy No
Topology.close destroyServer, stateTransition** No; stateTransition** can technically throw, but like in Server.destroy, Topology.close is written such that it never tries to make an illegal state transition

Note:

stateTransition* refers to the local function using the static state machine defined for the Server class created by the makeStateMachine helper.

stateTransition** refers to the local function using the static state machine defined for the Topology class created by the makeStateMachine helper.

connection.onError(new PoolClearedOnNetworkError(this));
this.checkIn(connection);
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is right. Good catch.

I think the reason this worked in the past was because

  1. checkIn calls destroyConnection(), which calls connection.destroy() in a process.nextTick()
  2. then `connection.onError() runs and shuts down the connection
  3. then connection.destroy() is a no-op because the connection has already been destroyed

You made destroyConnection synchronous, so we need to run onError first to ensure the connection is closed with the correct error. Then we run checkIn, which resets the pool state and calls connection.destroy(), which is a no-op.

@nbbeeken nbbeeken merged commit 36fa752 into main Mar 11, 2024
21 of 25 checks passed
@nbbeeken nbbeeken deleted the NODE-5915/refactor-topology-close-to-be-synchronous branch March 11, 2024 17:35
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
4 participants