diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown index 42e347f43a..6c9b727512 100644 --- a/doc/api/fs.markdown +++ b/doc/api/fs.markdown @@ -334,13 +334,20 @@ Returns a new [`WriteStream`][] object. (See [Writable Stream][]). { flags: 'w', defaultEncoding: 'utf8', fd: null, - mode: 0o666 } + mode: 0o666, + autoClose: true } `options` may also include a `start` option to allow writing data at some position past the beginning of the file. Modifying a file rather than replacing it may require a `flags` mode of `r+` rather than the default mode `w`. The `defaultEncoding` can be any one of those accepted by [`Buffer`][]. +If `autoClose` is set to true (default behavior) on `error` or `end` +the file descriptor will be closed automatically. If `autoClose` is false, +then the file descriptor won't be closed, even if there's an error. +It is your responsiblity to close it and make sure +there's no file descriptor leak. + Like [`ReadStream`][], if `fd` is specified, `WriteStream` will ignore the `path` argument and will use the specified file descriptor. This means that no `'open'` event will be emitted. Note that `fd` should be blocking; non-blocking diff --git a/lib/fs.js b/lib/fs.js index fbb2f47b0f..54e6b4ac0d 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1900,6 +1900,7 @@ function WriteStream(path, options) { this.mode = options.mode === undefined ? 0o666 : options.mode; this.start = options.start; + this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; this.pos = undefined; this.bytesWritten = 0; @@ -1921,7 +1922,11 @@ function WriteStream(path, options) { this.open(); // dispose on finish. - this.once('finish', this.close); + this.once('finish', function() { + if (this.autoClose) { + this.close(); + } + }); } fs.FileWriteStream = fs.WriteStream; // support the legacy name @@ -1930,7 +1935,9 @@ fs.FileWriteStream = fs.WriteStream; // support the legacy name WriteStream.prototype.open = function() { fs.open(this.path, this.flags, this.mode, function(er, fd) { if (er) { - this.destroy(); + if (this.autoClose) { + this.destroy(); + } this.emit('error', er); return; } @@ -1953,7 +1960,9 @@ WriteStream.prototype._write = function(data, encoding, cb) { var self = this; fs.write(this.fd, data, 0, data.length, this.pos, function(er, bytes) { if (er) { - self.destroy(); + if (self.autoClose) { + self.destroy(); + } return cb(er); } self.bytesWritten += bytes; diff --git a/test/parallel/test-fs-write-stream-autoclose-option.js b/test/parallel/test-fs-write-stream-autoclose-option.js new file mode 100644 index 0000000000..0706ed6485 --- /dev/null +++ b/test/parallel/test-fs-write-stream-autoclose-option.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const file = path.join(common.tmpDir, 'write-autoclose-opt1.txt'); +common.refreshTmpDir(); +let stream = fs.createWriteStream(file, {flags: 'w+', autoClose: false}); +stream.write('Test1'); +stream.end(); +stream.on('finish', common.mustCall(function() { + process.nextTick(common.mustCall(function() { + assert.strictEqual(stream.closed, undefined); + assert(stream.fd !== null); + next(); + })); +})); + +function next() { + // This will tell us if the fd is usable again or not + stream = fs.createWriteStream(null, {fd: stream.fd, start: 0}); + stream.write('Test2'); + stream.end(); + stream.on('finish', common.mustCall(function() { + assert.strictEqual(stream.closed, true); + assert.strictEqual(stream.fd, null); + process.nextTick(common.mustCall(next2)); + })); +} + +function next2() { + // This will test if after reusing the fd data is written properly + fs.readFile(file, function(err, data) { + assert(!err); + assert.strictEqual(data.toString(), 'Test2'); + process.nextTick(common.mustCall(next3)); + }); +} + +function next3() { + // This is to test success scenario where autoClose is true + const stream = fs.createWriteStream(file, {autoClose: true}); + stream.write('Test3'); + stream.end(); + stream.on('finish', common.mustCall(function() { + process.nextTick(common.mustCall(function() { + assert.strictEqual(stream.closed, true); + assert.strictEqual(stream.fd, null); + })); + })); +}