mirror of
https://github.com/d0zingcat/cors-anywhere.git
synced 2026-05-13 15:09:25 +00:00
Add tests for memory leaks
Using the performNRequests, I collected the following statistics
before choosing the maximum allowed "leaked" memory.
Node.js 0.12.2,
Using the http module ('use-http-instead-of-cors-anywhere'):
Memory usage delta: 132800 (100 requests of 50 kb each, 250ms)
Memory usage delta: 110144 (100 requests of 1 kb each, 172ms)
Memory usage delta: 709936 (1000 requests of 1 kb each, 902ms)
Memory usage delta: 865104 (10000 requests of 1 kb each, 7073ms)
Memory usage delta: 930416 (100000 requests of 1 kb each, 62856ms)
Using CORS Anywhere:
Memory usage delta: 356784 (100 requests of 50 kb each, 1004ms)
Memory usage delta: 355248 (100 requests of 1 kb each, 641ms)
Memory usage delta: 1326856 (1000 requests of 1 kb each, 3338ms)
Memory usage delta: 1462584 (10000 requests of 1 kb each, 21186ms)
Memory usage delta: 1473624 (100000 requests of 1 kb each, 211202ms)
Clearly, there is a small leak, but it is not proportional/linear
in terms of the number of requests, so the observed "leak" is probably
not an issue. Furthermore, the "leak" also occurs with the plain
http module.
After setting fixed limits, I ran the tests on Node.js 0.10.25 and
observed that the tests failed due to the too low limits, so I
incremented the limits (400 -> 550, 1500 -> 2000).
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
"supertest": "~0.15.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha ./test/test.js --reporter spec"
|
||||
"test": "./node_modules/.bin/mocha ./test/test*.js --reporter spec"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.6",
|
||||
|
||||
64
test/child.js
Normal file
64
test/child.js
Normal file
@@ -0,0 +1,64 @@
|
||||
// When this module is loaded, CORS Anywhere is started.
|
||||
// Then, a request is generated to warm up the server (just in case).
|
||||
// Then the base URL of CORS Anywhere is sent to the parent process.
|
||||
// ...
|
||||
// When the parent process is done, it sends an empty message to this child
|
||||
// process, which in turn records the change in used heap space.
|
||||
// The difference in heap space is finally sent back to the parent process.
|
||||
// ...
|
||||
// The parent process should then kill this child.
|
||||
|
||||
process.on('uncaughtException', function(e) {
|
||||
console.error('Uncaught exception in child process: ' + e);
|
||||
console.error(e.stack);
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
// Invoke memoryUsage() without using its result to make sure that any internal
|
||||
// datastructures that supports memoryUsage() is initialized and won't pollute
|
||||
// the memory usage measurement later on.
|
||||
process.memoryUsage();
|
||||
|
||||
var heapUsedStart = 0;
|
||||
function getMemoryUsage(callback) {
|
||||
// Note: Requires --expose-gc
|
||||
// 6 is the minimum amount of gc() calls before calling gc() again does not
|
||||
// reduce memory any more.
|
||||
for (var i = 0; i < 6; ++i) {
|
||||
global.gc();
|
||||
}
|
||||
callback(process.memoryUsage().heapUsed);
|
||||
}
|
||||
|
||||
var server;
|
||||
if (process.argv.indexOf('use-http-instead-of-cors-anywhere') >= 0) {
|
||||
server = require('http').createServer(function(req, res) { res.end(); });
|
||||
} else {
|
||||
server = require('../').createServer();
|
||||
}
|
||||
|
||||
server.listen(0, function() {
|
||||
// Perform 1 request to warm up.
|
||||
require('http').get({
|
||||
hostname: '127.0.0.1',
|
||||
port: server.address().port,
|
||||
path: '/http://invalid:99999',
|
||||
agent: false,
|
||||
}, function() {
|
||||
notifyParent();
|
||||
});
|
||||
|
||||
function notifyParent() {
|
||||
getMemoryUsage(function(usage) {
|
||||
heapUsedStart = usage;
|
||||
process.send('http://127.0.0.1:' + server.address().port + '/');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
process.once('message', function() {
|
||||
getMemoryUsage(function(heapUsedEnd) {
|
||||
var delta = heapUsedEnd - heapUsedStart;
|
||||
process.send(delta);
|
||||
});
|
||||
});
|
||||
110
test/test-memory.js
Normal file
110
test/test-memory.js
Normal file
@@ -0,0 +1,110 @@
|
||||
// Run this specific test using:
|
||||
// npm test -- -f memory
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var url = require('url');
|
||||
var fork = require('child_process').fork;
|
||||
|
||||
describe('memory usage', function() {
|
||||
var cors_api_url;
|
||||
|
||||
var server;
|
||||
var cors_anywhere_child;
|
||||
before(function(done) {
|
||||
server = http.createServer(function(req, res) {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}).listen(0, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
server.close(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function(done) {
|
||||
var cors_module_path = path.join(__dirname, 'child');
|
||||
var args = [];
|
||||
// Uncomment this if you want to compare the performance of CORS Anywhere
|
||||
// with the standard no-op http module.
|
||||
// args.push('use-http-instead-of-cors-anywhere');
|
||||
cors_anywhere_child = fork(cors_module_path, args, {
|
||||
execArgv: ['--expose-gc'],
|
||||
});
|
||||
cors_anywhere_child.once('message', function(cors_url) {
|
||||
cors_api_url = cors_url;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
cors_anywhere_child.kill();
|
||||
});
|
||||
|
||||
/**
|
||||
* Perform N CORS Anywhere proxy requests to a simple test server.
|
||||
*
|
||||
* @param {number} n - number of repetitions.
|
||||
* @param {number} requestSize - Approximate size of request in kilobytes.
|
||||
* @param {number} memMax - Expected maximum memory usage in kilobytes.
|
||||
* @param {function} done - Upon success, called without arguments.
|
||||
* Upon failure, called with the error as parameter.
|
||||
*/
|
||||
function performNRequests(n, requestSize, memMax, done) {
|
||||
var remaining = n;
|
||||
var request = url.parse(
|
||||
cors_api_url + 'http://127.0.0.1:' + server.address().port);
|
||||
request.agent = false; // Force Connection: Close
|
||||
request.headers = {
|
||||
'Long header': new Array(requestSize * 1e3).join('x'),
|
||||
};
|
||||
(function requestAgain() {
|
||||
if (remaining-- === 0) {
|
||||
cors_anywhere_child.once('message', function(memory_usage_delta) {
|
||||
console.log('Memory usage delta: ' + memory_usage_delta +
|
||||
' (' + n + ' requests of ' + requestSize + ' kb each)');
|
||||
if (memory_usage_delta > memMax * 1e3) {
|
||||
// Note: Even if this error is reached, always profile (e.g. using
|
||||
// node-inspector) whether it is a true leak, and not e.g. noise
|
||||
// caused by the implementation of V8/Node.js.
|
||||
// Uncomment args.push('use-http-instead-of-cors-anywhere') at the
|
||||
// fork() call to get a sense of what's normal.
|
||||
throw new Error('Possible memory leak: ' + memory_usage_delta +
|
||||
' bytes was not released, which exceeds the ' + memMax +
|
||||
' kb limit by ' +
|
||||
Math.round(memory_usage_delta / memMax / 10 - 100) + '%.');
|
||||
}
|
||||
done();
|
||||
});
|
||||
cors_anywhere_child.send(null);
|
||||
return;
|
||||
}
|
||||
http.request(request, function() {
|
||||
requestAgain();
|
||||
}).on('error', function(error) {
|
||||
done(error);
|
||||
}).end();
|
||||
})();
|
||||
}
|
||||
|
||||
it('100 GET requests 50k', function(done) {
|
||||
// This test is just for comparison with the following tests.
|
||||
performNRequests(100, 50, 550, done);
|
||||
});
|
||||
|
||||
// 100x 1k and 100x 50k for comparison.
|
||||
// Both should use about the same amount of memory if there is no leak.
|
||||
it('1000 GET requests 1k', function(done) {
|
||||
// Every request should complete within 10ms.
|
||||
this.timeout(1000 * 10);
|
||||
performNRequests(1000, 1, 2000, done);
|
||||
});
|
||||
it('1000 GET requests 50k', function(done) {
|
||||
// Every request should complete within 10ms.
|
||||
this.timeout(1000 * 10);
|
||||
performNRequests(1000, 50, 2000, done);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user