-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathresource_clean_up.test.ts
122 lines (109 loc) · 4.54 KB
/
resource_clean_up.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import * as v8 from 'node:v8';
import { expect } from 'chai';
import { sleep } from '../../tools/utils';
import { runScript } from './resource_tracking_script_builder';
/**
* This 5MB range is selected arbitrarily and should likely be raised if failures are seen intermittently.
*
* The goal here is to catch unbounded memory growth, currently runScript defaults to 100 iterations of whatever test code is passed in
* More than a 5MB growth in memory usage after the script has finished and _should have_ cleaned up all its resources likely indicates that
* the growth will continue if the script is changed to iterate the the test code more.
*/
const MB_PERMITTED_OFFSET = 5;
describe('Driver Resources', () => {
let startingMemoryUsed;
let endingMemoryUsed;
let heap;
beforeEach(function () {
if (globalThis.AbortController == null) {
if (this.currentTest) this.currentTest.skipReason = 'Need AbortController to run this test';
this.currentTest?.skip();
}
if (typeof this.configuration.serverApi === 'string') {
if (this.currentTest) {
this.currentTest.skipReason = 'runScript does not support serverApi settings';
}
this.currentTest?.skip();
}
});
context('on MongoClient.close()', () => {
before('create leak reproduction script', async function () {
if (globalThis.AbortController == null || typeof this.configuration.serverApi === 'string') {
return;
}
try {
const res = await runScript(
'no_resource_leak_connect_close',
this.configuration,
async function run({ MongoClient, uri }) {
const mongoClient = new MongoClient(uri, { minPoolSize: 100 });
await mongoClient.connect();
// Any operations will reproduce the issue found in v5.0.0/v4.13.0
// it would seem the MessageStream has to be used?
await mongoClient.db().command({ ping: 1 });
await mongoClient.close();
}
);
startingMemoryUsed = res.startingMemoryUsed;
endingMemoryUsed = res.endingMemoryUsed;
heap = res.heap;
} catch (error) {
// We don't expect the process execution to ever fail,
// leaving helpful debugging if we see this in CI
console.log(`runScript error message: ${error.message}`);
console.log(`runScript error stack: ${error.stack}`);
console.log(`runScript error cause: ${error.cause}`);
// Fail the test
this.test?.error(error);
}
});
describe('ending memory usage', () => {
it(`is within ${MB_PERMITTED_OFFSET}MB of starting amount`, async () => {
// Why check the lower bound? No reason, but it would be very surprising if we managed to free MB_PERMITTED_OFFSET MB of memory
// I expect us to **never** be below the lower bound, but I'd want to know if it happened
expect(
endingMemoryUsed,
`script started with ${startingMemoryUsed}MB heap but ended with ${endingMemoryUsed}MB heap used`
).to.be.within(
startingMemoryUsed - MB_PERMITTED_OFFSET,
startingMemoryUsed + MB_PERMITTED_OFFSET
);
});
});
describe('ending heap snapshot', () => {
it('has 0 MongoClients in memory', async () => {
const clients = heap.nodes.filter(n => n.name === 'MongoClient' && n.type === 'object');
// lengthOf crashes chai b/c it tries to print out a gigantic diff
expect(
clients.length,
`expected no MongoClients in the heapsnapshot, found ${clients.length}`
).to.equal(0);
});
});
});
context('when 100s of operations are executed and complete', () => {
beforeEach(function () {
if (this.currentTest && typeof v8.queryObjects !== 'function') {
this.currentTest.skipReason = 'Test requires v8.queryObjects API to count Promises';
this.currentTest?.skip();
}
});
let client;
beforeEach(async function () {
client = this.configuration.newClient();
});
afterEach(async function () {
await client.close();
});
it('does not leave behind additional promises', async () => {
const test = client.db('test').collection('test');
const promiseCountBefore = v8.queryObjects(Promise, { format: 'count' });
for (let i = 0; i < 100; i++) {
await test.findOne();
}
await sleep(10);
const promiseCountAfter = v8.queryObjects(Promise, { format: 'count' });
expect(promiseCountAfter).to.be.within(promiseCountBefore - 5, promiseCountBefore + 5);
});
});
});