2 var polyfills = require('./polyfills.js')
3 var legacy = require('./legacy-streams.js')
4 var clone = require('./clone.js')
6 var util = require('util')
8 /* istanbul ignore next - node 0.x polyfill */
12 /* istanbul ignore else - node 0.x polyfill */
13 if (typeof Symbol === 'function' && typeof Symbol.for === 'function') {
14 gracefulQueue = Symbol.for('graceful-fs.queue')
15 // This is used in testing by future versions
16 previousSymbol = Symbol.for('graceful-fs.previous')
18 gracefulQueue = '___graceful-fs.queue'
19 previousSymbol = '___graceful-fs.previous'
24 function publishQueue(context, queue) {
25 Object.defineProperty(context, gracefulQueue, {
34 debug = util.debuglog('gfs4')
35 else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || ''))
37 var m = util.format.apply(util, arguments)
38 m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ')
42 // Once time initialization
43 if (!fs[gracefulQueue]) {
44 // This queue can be shared by multiple loaded instances
45 var queue = global[gracefulQueue] || []
46 publishQueue(fs, queue)
48 // Patch fs.close/closeSync to shared queue version, because we need
49 // to retry() whenever a close happens *anywhere* in the program.
50 // This is essential when multiple graceful-fs instances are
51 // in play at the same time.
52 fs.close = (function (fs$close) {
53 function close (fd, cb) {
54 return fs$close.call(fs, fd, function (err) {
55 // This function uses the graceful-fs shared queue
60 if (typeof cb === 'function')
61 cb.apply(this, arguments)
65 Object.defineProperty(close, previousSymbol, {
71 fs.closeSync = (function (fs$closeSync) {
72 function closeSync (fd) {
73 // This function uses the graceful-fs shared queue
74 fs$closeSync.apply(fs, arguments)
78 Object.defineProperty(closeSync, previousSymbol, {
84 if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
85 process.on('exit', function() {
86 debug(fs[gracefulQueue])
87 require('assert').equal(fs[gracefulQueue].length, 0)
92 if (!global[gracefulQueue]) {
93 publishQueue(global, fs[gracefulQueue]);
96 module.exports = patch(clone(fs))
97 if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
98 module.exports = patch(fs)
102 function patch (fs) {
103 // Everything that references the open() function needs to be in here
105 fs.gracefulify = patch
107 fs.createReadStream = createReadStream
108 fs.createWriteStream = createWriteStream
109 var fs$readFile = fs.readFile
110 fs.readFile = readFile
111 function readFile (path, options, cb) {
112 if (typeof options === 'function')
113 cb = options, options = null
115 return go$readFile(path, options, cb)
117 function go$readFile (path, options, cb, startTime) {
118 return fs$readFile(path, options, function (err) {
119 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
120 enqueue([go$readFile, [path, options, cb], err, startTime || Date.now(), Date.now()])
122 if (typeof cb === 'function')
123 cb.apply(this, arguments)
129 var fs$writeFile = fs.writeFile
130 fs.writeFile = writeFile
131 function writeFile (path, data, options, cb) {
132 if (typeof options === 'function')
133 cb = options, options = null
135 return go$writeFile(path, data, options, cb)
137 function go$writeFile (path, data, options, cb, startTime) {
138 return fs$writeFile(path, data, options, function (err) {
139 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
140 enqueue([go$writeFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()])
142 if (typeof cb === 'function')
143 cb.apply(this, arguments)
149 var fs$appendFile = fs.appendFile
151 fs.appendFile = appendFile
152 function appendFile (path, data, options, cb) {
153 if (typeof options === 'function')
154 cb = options, options = null
156 return go$appendFile(path, data, options, cb)
158 function go$appendFile (path, data, options, cb, startTime) {
159 return fs$appendFile(path, data, options, function (err) {
160 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
161 enqueue([go$appendFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()])
163 if (typeof cb === 'function')
164 cb.apply(this, arguments)
170 var fs$copyFile = fs.copyFile
172 fs.copyFile = copyFile
173 function copyFile (src, dest, flags, cb) {
174 if (typeof flags === 'function') {
178 return go$copyFile(src, dest, flags, cb)
180 function go$copyFile (src, dest, flags, cb, startTime) {
181 return fs$copyFile(src, dest, flags, function (err) {
182 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
183 enqueue([go$copyFile, [src, dest, flags, cb], err, startTime || Date.now(), Date.now()])
185 if (typeof cb === 'function')
186 cb.apply(this, arguments)
192 var fs$readdir = fs.readdir
194 function readdir (path, options, cb) {
195 if (typeof options === 'function')
196 cb = options, options = null
198 return go$readdir(path, options, cb)
200 function go$readdir (path, options, cb, startTime) {
201 return fs$readdir(path, options, function (err, files) {
202 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
203 enqueue([go$readdir, [path, options, cb], err, startTime || Date.now(), Date.now()])
205 if (files && files.sort)
208 if (typeof cb === 'function')
209 cb.call(this, err, files)
215 if (process.version.substr(0, 4) === 'v0.8') {
216 var legStreams = legacy(fs)
217 ReadStream = legStreams.ReadStream
218 WriteStream = legStreams.WriteStream
221 var fs$ReadStream = fs.ReadStream
223 ReadStream.prototype = Object.create(fs$ReadStream.prototype)
224 ReadStream.prototype.open = ReadStream$open
227 var fs$WriteStream = fs.WriteStream
228 if (fs$WriteStream) {
229 WriteStream.prototype = Object.create(fs$WriteStream.prototype)
230 WriteStream.prototype.open = WriteStream$open
233 Object.defineProperty(fs, 'ReadStream', {
237 set: function (val) {
243 Object.defineProperty(fs, 'WriteStream', {
247 set: function (val) {
255 var FileReadStream = ReadStream
256 Object.defineProperty(fs, 'FileReadStream', {
258 return FileReadStream
260 set: function (val) {
266 var FileWriteStream = WriteStream
267 Object.defineProperty(fs, 'FileWriteStream', {
269 return FileWriteStream
271 set: function (val) {
272 FileWriteStream = val
278 function ReadStream (path, options) {
279 if (this instanceof ReadStream)
280 return fs$ReadStream.apply(this, arguments), this
282 return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
285 function ReadStream$open () {
287 open(that.path, that.flags, that.mode, function (err, fd) {
292 that.emit('error', err)
295 that.emit('open', fd)
301 function WriteStream (path, options) {
302 if (this instanceof WriteStream)
303 return fs$WriteStream.apply(this, arguments), this
305 return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
308 function WriteStream$open () {
310 open(that.path, that.flags, that.mode, function (err, fd) {
313 that.emit('error', err)
316 that.emit('open', fd)
321 function createReadStream (path, options) {
322 return new fs.ReadStream(path, options)
325 function createWriteStream (path, options) {
326 return new fs.WriteStream(path, options)
329 var fs$open = fs.open
331 function open (path, flags, mode, cb) {
332 if (typeof mode === 'function')
333 cb = mode, mode = null
335 return go$open(path, flags, mode, cb)
337 function go$open (path, flags, mode, cb, startTime) {
338 return fs$open(path, flags, mode, function (err, fd) {
339 if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
340 enqueue([go$open, [path, flags, mode, cb], err, startTime || Date.now(), Date.now()])
342 if (typeof cb === 'function')
343 cb.apply(this, arguments)
352 function enqueue (elem) {
353 debug('ENQUEUE', elem[0].name, elem[1])
354 fs[gracefulQueue].push(elem)
358 // keep track of the timeout between retry() calls
361 // reset the startTime and lastTime to now
362 // this resets the start of the 60 second overall timeout as well as the
363 // delay between attempts so that we'll retry these jobs sooner
364 function resetQueue () {
366 for (var i = 0; i < fs[gracefulQueue].length; ++i) {
367 // entries that are only a length of 2 are from an older version, don't
368 // bother modifying those since they'll be retried anyway.
369 if (fs[gracefulQueue][i].length > 2) {
370 fs[gracefulQueue][i][3] = now // startTime
371 fs[gracefulQueue][i][4] = now // lastTime
374 // call retry to make sure we're actively processing the queue
379 // clear the timer and remove it to help prevent unintended concurrency
380 clearTimeout(retryTimer)
381 retryTimer = undefined
383 if (fs[gracefulQueue].length === 0)
386 var elem = fs[gracefulQueue].shift()
389 // these items may be unset if they were added by an older graceful-fs
391 var startTime = elem[3]
392 var lastTime = elem[4]
394 // if we don't have a startTime we have no way of knowing if we've waited
395 // long enough, so go ahead and retry this item now
396 if (startTime === undefined) {
397 debug('RETRY', fn.name, args)
399 } else if (Date.now() - startTime >= 60000) {
400 // it's been more than 60 seconds total, bail now
401 debug('TIMEOUT', fn.name, args)
403 if (typeof cb === 'function')
406 // the amount of time between the last attempt and right now
407 var sinceAttempt = Date.now() - lastTime
408 // the amount of time between when we first tried, and when we last tried
409 // rounded up to at least 1
410 var sinceStart = Math.max(lastTime - startTime, 1)
411 // backoff. wait longer than the total time we've been retrying, but only
412 // up to a maximum of 100ms
413 var desiredDelay = Math.min(sinceStart * 1.2, 100)
414 // it's been long enough since the last retry, do it again
415 if (sinceAttempt >= desiredDelay) {
416 debug('RETRY', fn.name, args)
417 fn.apply(null, args.concat([startTime]))
419 // if we can't do this job yet, push it to the end of the queue
420 // and let the next iteration check again
421 fs[gracefulQueue].push(elem)
425 // schedule our next run if one isn't already scheduled
426 if (retryTimer === undefined) {
427 retryTimer = setTimeout(retry, 0)