Skip to content

Commit 3216d33

Browse files
test(NODE-6620): client.close() interrupts file reads (#4355)
1 parent c392465 commit 3216d33

File tree

5 files changed

+526
-15
lines changed

5 files changed

+526
-15
lines changed
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/* eslint-disable @typescript-eslint/no-empty-function */
2+
import { type TestConfiguration } from '../../tools/runner/config';
3+
import { runScriptAndGetProcessInfo } from './resource_tracking_script_builder';
4+
5+
describe.skip('MongoClient.close() Integration', () => {
6+
// note: these tests are set-up in accordance of the resource ownership tree
7+
8+
let config: TestConfiguration;
9+
10+
beforeEach(function () {
11+
config = this.configuration;
12+
});
13+
14+
describe('Node.js resource: TLS File read', () => {
15+
describe('when client is connecting and reads an infinite TLS file', () => {
16+
it('the file read is interrupted by client.close()', async function () {
17+
await runScriptAndGetProcessInfo(
18+
'tls-file-read',
19+
config,
20+
async function run({ MongoClient, uri, expect }) {
21+
const infiniteFile = '/dev/zero';
22+
const client = new MongoClient(uri, { tls: true, tlsCertificateKeyFile: infiniteFile });
23+
const connectPromise = client.connect();
24+
expect(process.getActiveResourcesInfo()).to.include('FSReqPromise');
25+
await client.close();
26+
expect(process.getActiveResourcesInfo()).to.not.include('FSReqPromise');
27+
const err = await connectPromise.catch(e => e);
28+
expect(err).to.exist;
29+
}
30+
);
31+
});
32+
});
33+
});
34+
35+
describe('MongoClientAuthProviders', () => {
36+
describe('Node.js resource: Token file read', () => {
37+
let tokenFileEnvCache;
38+
39+
beforeEach(function () {
40+
if (process.env.AUTH === 'auth') {
41+
this.currentTest.skipReason = 'OIDC test environment requires auth disabled';
42+
return this.skip();
43+
}
44+
tokenFileEnvCache = process.env.OIDC_TOKEN_FILE;
45+
});
46+
47+
afterEach(function () {
48+
process.env.OIDC_TOKEN_FILE = tokenFileEnvCache;
49+
});
50+
51+
describe('when MongoClientAuthProviders is instantiated and token file read hangs', () => {
52+
it('the file read is interrupted by client.close()', async () => {
53+
await runScriptAndGetProcessInfo(
54+
'token-file-read',
55+
config,
56+
async function run({ MongoClient, uri, expect }) {
57+
const infiniteFile = '/dev/zero';
58+
process.env.OIDC_TOKEN_FILE = infiniteFile;
59+
const options = {
60+
authMechanismProperties: { ENVIRONMENT: 'test' },
61+
authMechanism: 'MONGODB-OIDC'
62+
};
63+
const client = new MongoClient(uri, options);
64+
client.connect();
65+
expect(process.getActiveResourcesInfo()).to.include('FSReqPromise');
66+
await client.close();
67+
expect(process.getActiveResourcesInfo()).to.not.include('FSReqPromise');
68+
}
69+
);
70+
});
71+
});
72+
});
73+
});
74+
75+
describe('Topology', () => {
76+
describe('Node.js resource: Server Selection Timer', () => {
77+
describe('after a Topology is created through client.connect()', () => {
78+
it.skip('server selection timers are cleaned up by client.close()', async () => {});
79+
});
80+
});
81+
82+
describe('Server', () => {
83+
describe('Monitor', () => {
84+
// connection monitoring is by default turned on - with the exception of load-balanced mode
85+
const metadata: MongoDBMetadataUI = {
86+
requires: {
87+
topology: ['single', 'replicaset', 'sharded']
88+
}
89+
};
90+
91+
describe('MonitorInterval', () => {
92+
describe('Node.js resource: Timer', () => {
93+
describe('after a new monitor is made', () => {
94+
it.skip('monitor interval timer is cleaned up by client.close()', async () => {});
95+
});
96+
97+
describe('after a heartbeat fails', () => {
98+
it.skip('the new monitor interval timer is cleaned up by client.close()', async () => {});
99+
});
100+
});
101+
});
102+
103+
describe('Connection Monitoring', () => {
104+
describe('Node.js resource: Socket', () => {
105+
it.skip('no sockets remain after client.close()', metadata, async function () {});
106+
});
107+
});
108+
109+
describe('RTT Pinger', () => {
110+
describe('Node.js resource: Timer', () => {
111+
describe('after entering monitor streaming mode ', () => {
112+
it.skip('the rtt pinger timer is cleaned up by client.close()', async () => {
113+
// helloReply has a topologyVersion defined
114+
});
115+
});
116+
});
117+
118+
describe('Connection', () => {
119+
describe('Node.js resource: Socket', () => {
120+
describe('when rtt monitoring is turned on', () => {
121+
it.skip('no sockets remain after client.close()', async () => {});
122+
});
123+
});
124+
});
125+
});
126+
});
127+
128+
describe('ConnectionPool', () => {
129+
describe('Node.js resource: minPoolSize timer', () => {
130+
describe('after new connection pool is created', () => {
131+
it.skip('the minPoolSize timer is cleaned up by client.close()', async () => {});
132+
});
133+
});
134+
135+
describe('Node.js resource: checkOut Timer', () => {
136+
// waitQueueTimeoutMS
137+
describe('after new connection pool is created', () => {
138+
it.skip('the wait queue timer is cleaned up by client.close()', async () => {});
139+
});
140+
});
141+
142+
describe('Connection', () => {
143+
describe('Node.js resource: Socket', () => {
144+
describe('after a connection is checked out', () => {
145+
it.skip('no sockets remain after client.close()', async () => {});
146+
});
147+
148+
describe('after a minPoolSize has been set on the ConnectionPool', () => {
149+
it.skip('no sockets remain after client.close()', async () => {});
150+
});
151+
});
152+
});
153+
});
154+
});
155+
156+
describe('SrvPoller', () => {
157+
describe('Node.js resource: Timer', () => {
158+
describe('after SRVPoller is created', () => {
159+
it.skip('timers are cleaned up by client.close()', async () => {});
160+
});
161+
});
162+
});
163+
});
164+
165+
describe('ClientSession (Implicit)', () => {
166+
describe('Server resource: LSID/ServerSession', () => {
167+
describe('after a clientSession is implicitly created and used', () => {
168+
it.skip('the server-side ServerSession is cleaned up by client.close()', async function () {});
169+
});
170+
});
171+
172+
describe('Server resource: Transactions', () => {
173+
describe('after a clientSession is implicitly created and used', () => {
174+
it.skip('the server-side transaction is cleaned up by client.close()', async function () {});
175+
});
176+
});
177+
});
178+
179+
describe('ClientSession (Explicit)', () => {
180+
describe('Server resource: LSID/ServerSession', () => {
181+
describe('after a clientSession is created and used', () => {
182+
it.skip('the server-side ServerSession is cleaned up by client.close()', async function () {});
183+
});
184+
});
185+
186+
describe('Server resource: Transactions', () => {
187+
describe('after a clientSession is created and used', () => {
188+
it.skip('the server-side transaction is cleaned up by client.close()', async function () {});
189+
});
190+
});
191+
});
192+
193+
describe('AutoEncrypter', () => {
194+
const metadata: MongoDBMetadataUI = {
195+
requires: {
196+
mongodb: '>=4.2.0',
197+
clientSideEncryption: true
198+
}
199+
};
200+
201+
describe('KMS Request', () => {
202+
describe('Node.js resource: TLS file read', () => {
203+
describe('when KMSRequest reads an infinite TLS file', () => {
204+
it('the file read is interrupted by client.close()', async () => {
205+
await runScriptAndGetProcessInfo(
206+
'tls-file-read-auto-encryption',
207+
config,
208+
async function run({ MongoClient, uri, expect, ClientEncryption, BSON }) {
209+
const infiniteFile = '/dev/zero';
210+
211+
const kmsProviders = BSON.EJSON.parse(process.env.CSFLE_KMS_PROVIDERS);
212+
const masterKey = {
213+
region: 'us-east-1',
214+
key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0'
215+
};
216+
const provider = 'aws';
217+
218+
const keyVaultClient = new MongoClient(uri);
219+
await keyVaultClient.connect();
220+
await keyVaultClient.db('keyvault').collection('datakeys');
221+
222+
const clientEncryption = new ClientEncryption(keyVaultClient, {
223+
keyVaultNamespace: 'keyvault.datakeys',
224+
kmsProviders
225+
});
226+
const dataKey = await clientEncryption.createDataKey(provider, { masterKey });
227+
228+
function getEncryptExtraOptions() {
229+
if (
230+
typeof process.env.CRYPT_SHARED_LIB_PATH === 'string' &&
231+
process.env.CRYPT_SHARED_LIB_PATH.length > 0
232+
) {
233+
return { cryptSharedLibPath: process.env.CRYPT_SHARED_LIB_PATH };
234+
}
235+
return {};
236+
}
237+
const schemaMap = {
238+
'db.coll': {
239+
bsonType: 'object',
240+
encryptMetadata: {
241+
keyId: [dataKey]
242+
},
243+
properties: {
244+
a: {
245+
encrypt: {
246+
bsonType: 'int',
247+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random',
248+
keyId: [dataKey]
249+
}
250+
}
251+
}
252+
}
253+
};
254+
const encryptionOptions = {
255+
autoEncryption: {
256+
keyVaultNamespace: 'keyvault.datakeys',
257+
kmsProviders,
258+
extraOptions: getEncryptExtraOptions(),
259+
schemaMap,
260+
tlsOptions: { aws: { tlsCAFile: infiniteFile } }
261+
}
262+
};
263+
264+
const encryptedClient = new MongoClient(uri, encryptionOptions);
265+
await encryptedClient.connect();
266+
267+
expect(process.getActiveResourcesInfo()).to.not.include('FSReqPromise');
268+
269+
const insertPromise = encryptedClient
270+
.db('db')
271+
.collection('coll')
272+
.insertOne({ a: 1 });
273+
274+
expect(process.getActiveResourcesInfo()).to.include('FSReqPromise');
275+
276+
await keyVaultClient.close();
277+
await encryptedClient.close();
278+
279+
expect(process.getActiveResourcesInfo()).to.not.include('FSReqPromise');
280+
281+
const err = await insertPromise.catch(e => e);
282+
expect(err).to.exist;
283+
expect(err.errmsg).to.contain('Error in KMS response');
284+
}
285+
);
286+
});
287+
});
288+
});
289+
290+
describe('Node.js resource: Socket', () => {
291+
it.skip('no sockets remain after client.close()', metadata, async () => {});
292+
});
293+
});
294+
});
295+
296+
describe('Server resource: Cursor', () => {
297+
describe('after cursors are created', () => {
298+
it.skip('all active server-side cursors are closed by client.close()', async function () {});
299+
});
300+
});
301+
});

test/integration/node-specific/resource_clean_up.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as v8 from 'node:v8';
33
import { expect } from 'chai';
44

55
import { sleep } from '../../tools/utils';
6-
import { runScript } from './resource_tracking_script_builder';
6+
import { runScriptAndReturnHeapInfo } from './resource_tracking_script_builder';
77

88
/**
99
* This 5MB range is selected arbitrarily and should likely be raised if failures are seen intermittently.
@@ -38,7 +38,7 @@ describe('Driver Resources', () => {
3838
return;
3939
}
4040
try {
41-
const res = await runScript(
41+
const res = await runScriptAndReturnHeapInfo(
4242
'no_resource_leak_connect_close',
4343
this.configuration,
4444
async function run({ MongoClient, uri }) {

0 commit comments

Comments
 (0)