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

Fix memory leak #137

Merged
merged 5 commits into from
Nov 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
sudo: false
language: node_js
node_js:
- '0.10'
- '4'
- '5'
- '6'
- '7'
- '8'
- '9'
- '10'
4 changes: 1 addition & 3 deletions fs.js → clone.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict'

var fs = require('fs')

module.exports = clone(fs)
module.exports = clone

function clone (obj) {
if (obj === null || typeof obj !== 'object')
Expand Down
33 changes: 23 additions & 10 deletions graceful-fs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
var fs = require('fs')
var polyfills = require('./polyfills.js')
var legacy = require('./legacy-streams.js')
var clone = require('./clone.js')

Choose a reason for hiding this comment

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

missing clone.js


var queue = []

var util = require('util')
Expand All @@ -24,17 +26,17 @@ if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
})
}

module.exports = patch(require('./fs.js'))
if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) {
module.exports = patch(fs)
module.exports = patch(clone(fs))
if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
module.exports = patch(fs)
fs.__patched = true;
}

// Always patch fs.close/closeSync, because we want to
// retry() whenever a close happens *anywhere* in the program.
// This is essential when multiple graceful-fs instances are
// in play at the same time.
module.exports.close =
fs.close = (function (fs$close) { return function (fd, cb) {
module.exports.close = (function (fs$close) { return function (fd, cb) {
return fs$close.call(fs, fd, function (err) {
if (!err)
retry()
Expand All @@ -44,17 +46,27 @@ fs.close = (function (fs$close) { return function (fd, cb) {
})
}})(fs.close)

module.exports.closeSync =
fs.closeSync = (function (fs$closeSync) { return function (fd) {
module.exports.closeSync = (function (fs$closeSync) { return function (fd) {
// Note that graceful-fs also retries when fs.closeSync() fails.
// Looks like a bug to me, although it's probably a harmless one.
var rval = fs$closeSync.apply(fs, arguments)
retry()
return rval
}})(fs.closeSync)

// Only patch fs once, otherwise we'll run into a memory leak if
// graceful-fs is loaded multiple times, such as in test environments that
// reset the loaded modules between tests.
// We look for the string `graceful-fs` from the comment above. This
// way we are not adding any extra properties and it will detect if older
// versions of graceful-fs are installed.
if (!fs.closeSync.toString().includes('graceful-fs')) {
fs.closeSync = module.exports.closeSync;
fs.close = module.exports.close;
}

function patch (fs) {
// Everything that references the open() function needs to be in here
// Everything that references the open() function needs to be in here
polyfills(fs)
fs.gracefulify = patch
fs.FileReadStream = ReadStream; // Legacy name.
Expand Down Expand Up @@ -142,8 +154,9 @@ function patch (fs) {
if (files && files.sort)
files.sort()

if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
enqueue([go$readdir, [args]])
if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) {
enqueue([go$readdir, [args]])
}
else {
if (typeof cb === 'function')
cb.apply(this, arguments)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
],
"license": "ISC",
"devDependencies": {
"import-fresh": "^2.0.0",
"mkdirp": "^0.5.0",
"rimraf": "^2.2.8",
"tap": "^11.0.0"
Expand Down
4 changes: 2 additions & 2 deletions polyfills.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
var fs = require('./fs.js')
var constants = require('constants')

var origCwd = process.cwd
Expand Down Expand Up @@ -145,7 +144,7 @@ function patch (fs) {
}
}
}})(fs.readSync)
}


function patchLchmod (fs) {
fs.lchmod = function (path, mode, callback) {
Expand Down Expand Up @@ -328,3 +327,4 @@ function chownErOk (er) {

return false
}
}
6 changes: 3 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
var spawn = require('child_process').spawn
var fs = require('fs')
var tap = require('tap')
var dir = __dirname + '/test'
Expand All @@ -14,11 +13,12 @@ var env = Object.keys(process.env).reduce(function (env, k) {

files.filter(function (f) {
if (/\.js$/.test(f) && fs.statSync(dir + '/' + f).isFile()) {
tap.spawn(node, ['test/' + f])
// expose-gc is so we can check for memory leaks
tap.spawn(node, ['--expose-gc', 'test/' + f])
return true
}
}).forEach(function (f) {
tap.spawn(node, ['test/' + f], {
tap.spawn(node, ['--expose-gc', 'test/' + f], {
env: env
}, '🐵 test/' + f)
})
25 changes: 25 additions & 0 deletions test/avoid-memory-leak.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var importFresh = require('import-fresh');
var t = require('tap')

t.test('no memory leak when loading multiple times', function(t) {
t.plan(1);
importFresh(process.cwd() + '/graceful-fs.js') // node 0.10-5 were getting: Cannot find module '../'
const mbUsedBefore = process.memoryUsage().heapUsed / Math.pow(1024, 2);
// simulate project with 4000 tests
var i = 0;
function importFreshGracefulFs() {
importFresh(process.cwd() + '/graceful-fs.js');
if (i < 4000) {
i++;
process.nextTick(() => importFreshGracefulFs());
} else {
global.gc();
const mbUsedAfter = process.memoryUsage().heapUsed / Math.pow(1024, 2);
// We expect less than a 2 MB difference
const memoryUsageMB = Math.round(mbUsedAfter - mbUsedBefore);
t.ok(memoryUsageMB < 2, 'We expect less than 2MB difference, but ' + memoryUsageMB + 'MB is still claimed.');
t.end();
}
}
importFreshGracefulFs();
})
2 changes: 1 addition & 1 deletion test/readdir-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fs.readdir = function(path, options, cb) {
er.code = 'EMFILE'
cb(er)
process.nextTick(function () {
fs.closeSync(fs.openSync(__filename, 'r'))
g.closeSync(fs.openSync(__filename, 'r'))
})
})
return
Expand Down