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

test(NODE-6631): Implement integration tests for improved client.close() - sockets + timers #4361

Open
wants to merge 51 commits into
base: main
Choose a base branch
from

Conversation

aditi-khare-mongoDB
Copy link
Contributor

@aditi-khare-mongoDB aditi-khare-mongoDB commented Dec 30, 2024

Description

Add socket integration tests for client.close().

What is changing?

  • Assert socket creation and clean-up through libuv tracking for each relevant driver instance of socket creation.
  • Assert timer creation by asserting timers are defined on relevant client components and assert timer clean-up through process.getActiveResourceInfo()
    • Timer existence is more finicky to test, and require some artificial delaying of promises and high timer MSs
    • ex: a high heartbeatFrequencyMS to ensure we're not in between intervals when we assert MonitorInterval's timer exits

To see more information regarding testing plan outline see the design document, specifically the dropdowns under Sockets and Timers sections, respectively.

Is there new documentation needed for these changes?

No

What is the motivation for this change?

Improved client.close()

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

Copy link

There is an existing patch(es) for this commit SHA:

Please note that the status that is posted is not in the context of this PR but rather the (latest) existing patch and that may affect some tests that may depend on the particular PR. If your tests do not rely on any PR-specific values (like base or head branch name) then your tests will report the same status. If you would like a patch to run in the context of this PR and abort the other(s), comment 'evergreen retry'.

@aditi-khare-mongoDB aditi-khare-mongoDB changed the base branch from main to NODE-6620/implement-client-close-test December 30, 2024 20:39
@aditi-khare-mongoDB aditi-khare-mongoDB changed the base branch from NODE-6620/implement-client-close-test to NODE-6615/integration-client-close December 30, 2024 20:39
@aditi-khare-mongoDB aditi-khare-mongoDB changed the title test(NODE-6620): Add Improved Client.close() Socket Resource Integration Tests test(NODE-6620): Implement integration tests for improved client.close() - Socket Dec 30, 2024
@aditi-khare-mongoDB aditi-khare-mongoDB changed the title test(NODE-6620): Implement integration tests for improved client.close() - Socket test(NODE-6620): Implement integration tests for improved client.close() - Sockets Dec 30, 2024
@aditi-khare-mongoDB aditi-khare-mongoDB changed the title test(NODE-6631): Implement integration tests for improved client.close() - Sockets + Timers test(NODE-6631): Implement integration tests for improved client.close() - sockets + timers Jan 7, 2025
!originalReportAddresses.includes(resource.address) &&
resource.is_referenced && // if a resource is unreferenced, it's not keeping the event loop open
(!serverType.includes(resource.type) || resource.is_active)
!originalReportAddresses.includes(resource.address) && resource.is_referenced // if a resource is unreferenced, it's not keeping the event loop open
Copy link
Contributor Author

@aditi-khare-mongoDB aditi-khare-mongoDB Jan 7, 2025

Choose a reason for hiding this comment

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

Follow-up from NODE-6620: We do want to clean up inactive sockets per Slack discussion


const servers = client.topology?.s.servers;

// note: minPoolSizeCheckFrequencyMS = 100 ms by client, so this test has a chance of being flaky
Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the note on the comment. This test has a higher chance of being flaky since the client can't set minPoolSizeCheckFrequencyMS. I can this to set the variable to a higher value using sinon if preferred by reviewers.

Base automatically changed from NODE-6615/integration-client-close to main January 8, 2025 16:18
rebase
@aditi-khare-mongoDB aditi-khare-mongoDB force-pushed the NODE-6620/sockets branch 2 times, most recently from e97cc3c to 4865259 Compare January 10, 2025 18:27
updated tests

delete extraneous files

srv test

lint
@aditi-khare-mongoDB aditi-khare-mongoDB marked this pull request as ready for review January 10, 2025 18:31
@nbbeeken nbbeeken self-assigned this Jan 10, 2025
@nbbeeken nbbeeken added the Primary Review In Review with primary reviewer, not yet ready for team's eyes label Jan 10, 2025
@nbbeeken nbbeeken self-requested a review January 10, 2025 19:35
let serverSelectionTimeoutStarted = false;

// make server selection hang so check out timer isn't cleared and check that the timeout has started
sinon.stub(Promise, 'race').callsFake(async ([_serverPromise, timeout]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we test against a replicaset and use a ReadPreference that doesn't match any servers then we can be sure we're stuck in server selection until the timer fires. Then we won't need to sinon spy or stub here.

This is how I'm testing server selection interruption for signal support:
https://github.com/mongodb/node-mongodb-native/blob/NODE-6258-abortsignal/test/integration/node-specific/abort_signal.test.ts#L201-L244

I'm also seeing an error when I skip over the resource assertions:

TypeError: Cannot read properties of undefined (reading 'setTimeout')

Comment on lines +142 to +144
client.on('serverHeartbeatSucceeded', () => (heartbeatHappened = true));
await client.connect();
await sleep(heartbeatFrequencyMS * 2.5);
Copy link
Contributor

Choose a reason for hiding this comment

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

What if we awaited the 'serverHeartbeatSucceeded' event directly instead of sleeping for some amount of time?

Comment on lines +147 to +151
function getMonitorTimer(servers) {
for (const server of servers) {
return server[1]?.monitor.monitorId.timerId;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

what about topologies other than standalone should we be returning more than the first timer?

const servers = client.topology.s.servers;
expect(getMonitorTimer(servers)).to.exist;
await client.close();
expect(getMonitorTimer(servers)).to.not.exist;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't feel strongly but I don't think we need to check that it has been set to null/undefined because if it was left on there it wouldn't be wrong as long as it is still cleared in the "resource" sense.

Comment on lines +169 to +170
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
const run = async function ({ MongoClient, uri, expect, sleep, getTimerCount }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The nice part about giving all the inputs as an object that we can pick and chose the inputs by simply omitting the dependencies we aren't using. So if you remove uri then you can remove the lint exemption

};
await utilClient.db('admin').command(failPoint);

const timeoutStartedSpy = sinon.spy(timers, 'setTimeout');
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm seeing an error thrown for this the same way it was for the server selection test. I don't think we need the spy if we know we have a commandStarted on a client with maxPoolSize: 1

const timeoutStartedSpy = sinon.spy(timers, 'setTimeout');

const client = new MongoClient(uri, {
minPoolSize: 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

I would only set the max so that we don't create the timer for satisfying minPoolSize, not necessarily a problem but more resources that we need to make for this test.


await utilClient.close();

const err = await insertPromise;
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the first insertPromise succeed (just after a long time), its the second one that won't be able to complete connection checkout

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 was confused / surprised about this as well. If we await both of them, the first one fails and the second one succeeds.

process.report.getReport().libuv.filter(r => r.type === 'tcp');

// assert no sockets to start with
expect(connectionMonitoringReport()).to.have.length(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
expect(connectionMonitoringReport()).to.have.length(0);
expect(connectionMonitoringReport()).to.have.lengthOf(0);

I could be wrong but I think you have to use lengthOf

const infiniteFile = '/dev/zero';

const kmsProviders = BSON.EJSON.parse(process.env.CSFLE_KMS_PROVIDERS);
const kmsProviders = mongodb.BSON.EJSON.parse(process.env.CSFLE_KMS_PROVIDERS);
Copy link
Contributor

Choose a reason for hiding this comment

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

This test fails locally when CSFLE_KMS_PROVIDERS isn't defined, which is the default, can you put the metadata on this test to have it skip unless encryption is enabled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Primary Review In Review with primary reviewer, not yet ready for team's eyes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants