Skip to content

Commit e4bbbcc

Browse files
AnasAboreedaTrott
authored andcommitted
fs: add fs.writev() which exposes syscall writev()
fs with writev allow many buffers to be pushed to underlying OS APIs in one batch, so this should improve write speed to files. Refs: #2298 PR-URL: #25925 Fixes: #2298 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 5e3b4d6 commit e4bbbcc

5 files changed

Lines changed: 294 additions & 5 deletions

File tree

‎doc/api/fs.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3863,6 +3863,52 @@ changes:
38633863
For detailed information, see the documentation of the asynchronous version of
38643864
this API: [`fs.write(fd, string...)`][].
38653865

3866+
## fs.writev(fd, buffers[, position], callback)
3867+
<!-- YAML
3868+
added: REPLACEME
3869+
-->
3870+
3871+
* `fd` {integer}
3872+
* `buffers` {ArrayBufferView[]}
3873+
* `position` {integer}
3874+
* `callback` {Function}
3875+
* `err` {Error}
3876+
* `bytesWritten` {integer}
3877+
* `buffers` {ArrayBufferView[]}
3878+
3879+
Write an array of `ArrayBufferView`s to the file specified by `fd` using
3880+
`writev()`.
3881+
3882+
`position` is the offset from the beginning of the file where this data
3883+
should be written. If `typeof position !== 'number'`, the data will be written
3884+
at the current position.
3885+
3886+
The callback will be given three arguments: `err`, `bytesWritten`, and
3887+
`buffers`. `bytesWritten` is how many bytes were written from `buffers`.
3888+
3889+
If this method is [`util.promisify()`][]ed, it returns a `Promise` for an
3890+
`Object` with `bytesWritten` and `buffers` properties.
3891+
3892+
It is unsafe to use `fs.writev()` multiple times on the same file without
3893+
waiting for the callback. For this scenario, use [`fs.createWriteStream()`][].
3894+
3895+
On Linux, positional writes don't work when the file is opened in append mode.
3896+
The kernel ignores the position argument and always appends the data to
3897+
the end of the file.
3898+
3899+
## fs.writevSync(fd, buffers[, position])
3900+
<!-- YAML
3901+
added: REPLACEME
3902+
-->
3903+
3904+
* `fd` {integer}
3905+
* `buffers` {ArrayBufferView[]}
3906+
* `position` {integer}
3907+
* Returns: {number} The number of bytes written.
3908+
3909+
For detailed information, see the documentation of the asynchronous version of
3910+
this API: [`fs.writev()`][].
3911+
38663912
## fs Promises API
38673913

38683914
The `fs.promises` API provides an alternative set of asynchronous file system
@@ -5113,6 +5159,7 @@ the file contents.
51135159
[`fs.write(fd, buffer...)`]: #fs_fs_write_fd_buffer_offset_length_position_callback
51145160
[`fs.write(fd, string...)`]: #fs_fs_write_fd_string_position_encoding_callback
51155161
[`fs.writeFile()`]: #fs_fs_writefile_file_data_options_callback
5162+
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
51165163
[`inotify(7)`]: http://man7.org/linux/man-pages/man7/inotify.7.html
51175164
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
51185165
[`net.Socket`]: net.html#net_class_net_socket

‎lib/fs.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ function maybeCallback(cb) {
142142
throw new ERR_INVALID_CALLBACK(cb);
143143
}
144144

145+
function isBuffersArray(value) {
146+
if (!Array.isArray(value))
147+
return false;
148+
149+
for (var i = 0; i < value.length; i += 1) {
150+
if (!isArrayBufferView(value[i])) {
151+
return false;
152+
}
153+
}
154+
155+
return true;
156+
}
157+
145158
// Ensure that callbacks run in the global context. Only use this function
146159
// for callbacks that are passed to the binding layer, callbacks that are
147160
// invoked from JS already run in the proper scope.
@@ -572,7 +585,7 @@ function write(fd, buffer, offset, length, position, callback) {
572585
Object.defineProperty(write, internalUtil.customPromisifyArgs,
573586
{ value: ['bytesWritten', 'buffer'], enumerable: false });
574587

575-
// usage:
588+
// Usage:
576589
// fs.writeSync(fd, buffer[, offset[, length[, position]]]);
577590
// OR
578591
// fs.writeSync(fd, string[, position[, encoding]]);
@@ -605,6 +618,54 @@ function writeSync(fd, buffer, offset, length, position) {
605618
return result;
606619
}
607620

621+
// usage:
622+
// fs.writev(fd, buffers[, position], callback);
623+
function writev(fd, buffers, position, callback) {
624+
function wrapper(err, written) {
625+
callback(err, written || 0, buffers);
626+
}
627+
628+
validateUint32(fd, 'fd');
629+
630+
if (!isBuffersArray(buffers)) {
631+
throw new ERR_INVALID_ARG_TYPE('buffers', 'ArrayBufferView[]', buffers);
632+
}
633+
634+
const req = new FSReqCallback();
635+
req.oncomplete = wrapper;
636+
637+
callback = maybeCallback(callback || position);
638+
639+
if (typeof position !== 'number')
640+
position = null;
641+
642+
return binding.writeBuffers(fd, buffers, position, req);
643+
}
644+
645+
Object.defineProperty(writev, internalUtil.customPromisifyArgs, {
646+
value: ['bytesWritten', 'buffer'],
647+
enumerable: false
648+
});
649+
650+
// fs.writevSync(fd, buffers[, position]);
651+
function writevSync(fd, buffers, position) {
652+
653+
validateUint32(fd, 'fd');
654+
const ctx = {};
655+
656+
if (!isBuffersArray(buffers)) {
657+
throw new ERR_INVALID_ARG_TYPE('buffers', 'ArrayBufferView[]', buffers);
658+
}
659+
660+
if (typeof position !== 'number')
661+
position = null;
662+
663+
const result = binding.writeBuffers(fd, buffers, position, undefined, ctx);
664+
665+
handleErrorFromBinding(ctx);
666+
return result;
667+
}
668+
608669
function rename(oldPath, newPath, callback) {
609670
callback = makeCallback(callback);
610671
oldPath = getValidatedPath(oldPath, 'oldPath');
@@ -1841,6 +1902,8 @@ module.exports = fs = {
18411902
writeFileSync,
18421903
write,
18431904
writeSync,
1905+
writev,
1906+
writevSync,
18441907
Dirent,
18451908
Stats,
18461909

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
const fs = require('fs');
7+
const tmpdir = require('../common/tmpdir');
8+
9+
tmpdir.refresh();
10+
11+
const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف';
12+
13+
const getFileName = (i) => path.join(tmpdir.path, `writev_sync_${i}.txt`);
14+
15+
/**
16+
* Testing with a array of buffers input
17+
*/
18+
19+
// fs.writevSync with array of buffers with all parameters
20+
{
21+
const filename = getFileName(1);
22+
const fd = fs.openSync(filename, 'w');
23+
24+
const buffer = Buffer.from(expected);
25+
const bufferArr = [buffer, buffer];
26+
const expectedLength = bufferArr.length * buffer.byteLength;
27+
28+
let written = fs.writevSync(fd, [Buffer.from('')], null);
29+
assert.deepStrictEqual(written, 0);
30+
31+
written = fs.writevSync(fd, bufferArr, null);
32+
assert.deepStrictEqual(written, expectedLength);
33+
34+
fs.closeSync(fd);
35+
36+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
37+
}
38+
39+
// fs.writevSync with array of buffers without position
40+
{
41+
const filename = getFileName(2);
42+
const fd = fs.openSync(filename, 'w');
43+
44+
const buffer = Buffer.from(expected);
45+
const bufferArr = [buffer, buffer, buffer];
46+
const expectedLength = bufferArr.length * buffer.byteLength;
47+
48+
let written = fs.writevSync(fd, [Buffer.from('')]);
49+
assert.deepStrictEqual(written, 0);
50+
51+
written = fs.writevSync(fd, bufferArr);
52+
assert.deepStrictEqual(written, expectedLength);
53+
54+
fs.closeSync(fd);
55+
56+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
57+
}
58+
59+
/**
60+
* Testing with wrong input types
61+
*/
62+
{
63+
const filename = getFileName(3);
64+
const fd = fs.openSync(filename, 'w');
65+
66+
[false, 'test', {}, [{}], ['sdf'], null, undefined].forEach((i) => {
67+
common.expectsError(
68+
() => fs.writevSync(fd, i, null), {
69+
code: 'ERR_INVALID_ARG_TYPE',
70+
type: TypeError
71+
}
72+
);
73+
});
74+
75+
fs.closeSync(fd);
76+
}
77+
78+
// fs.writevSync with wrong fd types
79+
[false, 'test', {}, [{}], null, undefined].forEach((i) => {
80+
common.expectsError(
81+
() => fs.writevSync(i),
82+
{
83+
code: 'ERR_INVALID_ARG_TYPE',
84+
type: TypeError
85+
}
86+
);
87+
});

‎test/parallel/test-fs-writev.js‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
const fs = require('fs');
7+
const tmpdir = require('../common/tmpdir');
8+
9+
tmpdir.refresh();
10+
11+
const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف';
12+
13+
const getFileName = (i) => path.join(tmpdir.path, `writev_${i}.txt`);
14+
15+
/**
16+
* Testing with a array of buffers input
17+
*/
18+
19+
// fs.writev with array of buffers with all parameters
20+
{
21+
const filename = getFileName(1);
22+
const fd = fs.openSync(filename, 'w');
23+
24+
const buffer = Buffer.from(expected);
25+
const bufferArr = [buffer, buffer];
26+
27+
const done = common.mustCall((err, written, buffers) => {
28+
assert.ifError(err);
29+
30+
assert.deepStrictEqual(bufferArr, buffers);
31+
const expectedLength = bufferArr.length * buffer.byteLength;
32+
assert.deepStrictEqual(written, expectedLength);
33+
fs.closeSync(fd);
34+
35+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
36+
});
37+
38+
fs.writev(fd, bufferArr, null, done);
39+
}
40+
41+
// fs.writev with array of buffers without position
42+
{
43+
const filename = getFileName(2);
44+
const fd = fs.openSync(filename, 'w');
45+
46+
const buffer = Buffer.from(expected);
47+
const bufferArr = [buffer, buffer];
48+
49+
const done = common.mustCall((err, written, buffers) => {
50+
assert.ifError(err);
51+
52+
assert.deepStrictEqual(bufferArr, buffers);
53+
54+
const expectedLength = bufferArr.length * buffer.byteLength;
55+
assert.deepStrictEqual(written, expectedLength);
56+
fs.closeSync(fd);
57+
58+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
59+
});
60+
61+
fs.writev(fd, bufferArr, done);
62+
}
63+
64+
/**
65+
* Testing with wrong input types
66+
*/
67+
{
68+
const filename = getFileName(3);
69+
const fd = fs.openSync(filename, 'w');
70+
71+
[false, 'test', {}, [{}], ['sdf'], null, undefined].forEach((i) => {
72+
common.expectsError(
73+
() => fs.writev(fd, i, null, common.mustNotCall()), {
74+
code: 'ERR_INVALID_ARG_TYPE',
75+
type: TypeError
76+
}
77+
);
78+
});
79+
80+
fs.closeSync(fd);
81+
}
82+
83+
// fs.writev with wrong fd types
84+
[false, 'test', {}, [{}], null, undefined].forEach((i) => {
85+
common.expectsError(
86+
() => fs.writev(i, common.mustNotCall()),
87+
{
88+
code: 'ERR_INVALID_ARG_TYPE',
89+
type: TypeError
90+
}
91+
);
92+
});

‎tools/doc/type-parser.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ const jsPrimitives = {
1515

1616
const jsGlobalObjectsUrl = `${jsDocPrefix}Reference/Global_Objects/`;
1717
const jsGlobalTypes = [
18-
'Array', 'ArrayBuffer', 'DataView', 'Date', 'Error', 'EvalError', 'Function',
19-
'Map', 'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set',
20-
'SharedArrayBuffer', 'SyntaxError', 'TypeError', 'TypedArray', 'URIError',
21-
'Uint8Array',
18+
'Array', 'ArrayBuffer', 'ArrayBufferView', 'DataView', 'Date', 'Error',
19+
'EvalError', 'Function', 'Map', 'Object', 'Promise', 'RangeError',
20+
'ReferenceError', 'RegExp', 'Set', 'SharedArrayBuffer', 'SyntaxError',
21+
'TypeError', 'TypedArray', 'URIError', 'Uint8Array',
2222
];
2323

2424
const customTypesMap = {

0 commit comments

Comments
 (0)