Skip to content

Commit bb19d82

Browse files
AnasAboreedatargos
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 <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 5b892c4 commit bb19d82

File tree

5 files changed

+294
-5
lines changed

5 files changed

+294
-5
lines changed

doc/api/fs.md

+47
Original file line numberDiff line numberDiff line change
@@ -3801,6 +3801,52 @@ changes:
38013801
For detailed information, see the documentation of the asynchronous version of
38023802
this API: [`fs.write(fd, string...)`][].
38033803

3804+
## fs.writev(fd, buffers[, position], callback)
3805+
<!-- YAML
3806+
added: REPLACEME
3807+
-->
3808+
3809+
* `fd` {integer}
3810+
* `buffers` {ArrayBufferView[]}
3811+
* `position` {integer}
3812+
* `callback` {Function}
3813+
* `err` {Error}
3814+
* `bytesWritten` {integer}
3815+
* `buffers` {ArrayBufferView[]}
3816+
3817+
Write an array of `ArrayBufferView`s to the file specified by `fd` using
3818+
`writev()`.
3819+
3820+
`position` is the offset from the beginning of the file where this data
3821+
should be written. If `typeof position !== 'number'`, the data will be written
3822+
at the current position.
3823+
3824+
The callback will be given three arguments: `err`, `bytesWritten`, and
3825+
`buffers`. `bytesWritten` is how many bytes were written from `buffers`.
3826+
3827+
If this method is [`util.promisify()`][]ed, it returns a `Promise` for an
3828+
`Object` with `bytesWritten` and `buffers` properties.
3829+
3830+
It is unsafe to use `fs.writev()` multiple times on the same file without
3831+
waiting for the callback. For this scenario, use [`fs.createWriteStream()`][].
3832+
3833+
On Linux, positional writes don't work when the file is opened in append mode.
3834+
The kernel ignores the position argument and always appends the data to
3835+
the end of the file.
3836+
3837+
## fs.writevSync(fd, buffers[, position])
3838+
<!-- YAML
3839+
added: REPLACEME
3840+
-->
3841+
3842+
* `fd` {integer}
3843+
* `buffers` {ArrayBufferView[]}
3844+
* `position` {integer}
3845+
* Returns: {number} The number of bytes written.
3846+
3847+
For detailed information, see the documentation of the asynchronous version of
3848+
this API: [`fs.writev()`][].
3849+
38043850
## fs Promises API
38053851

38063852
The `fs.promises` API provides an alternative set of asynchronous file system
@@ -5051,6 +5097,7 @@ the file contents.
50515097
[`fs.write(fd, buffer...)`]: #fs_fs_write_fd_buffer_offset_length_position_callback
50525098
[`fs.write(fd, string...)`]: #fs_fs_write_fd_string_position_encoding_callback
50535099
[`fs.writeFile()`]: #fs_fs_writefile_file_data_options_callback
5100+
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
50545101
[`inotify(7)`]: http://man7.org/linux/man-pages/man7/inotify.7.html
50555102
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
50565103
[`net.Socket`]: net.html#net_class_net_socket

lib/fs.js

+64-1
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.
@@ -559,7 +572,7 @@ function write(fd, buffer, offset, length, position, callback) {
559572
Object.defineProperty(write, internalUtil.customPromisifyArgs,
560573
{ value: ['bytesWritten', 'buffer'], enumerable: false });
561574

562-
// usage:
575+
// Usage:
563576
// fs.writeSync(fd, buffer[, offset[, length[, position]]]);
564577
// OR
565578
// fs.writeSync(fd, string[, position[, encoding]]);
@@ -589,6 +602,54 @@ function writeSync(fd, buffer, offset, length, position) {
589602
return result;
590603
}
591604

605+
// usage:
606+
// fs.writev(fd, buffers[, position], callback);
607+
function writev(fd, buffers, position, callback) {
608+
function wrapper(err, written) {
609+
callback(err, written || 0, buffers);
610+
}
611+
612+
validateUint32(fd, 'fd');
613+
614+
if (!isBuffersArray(buffers)) {
615+
throw new ERR_INVALID_ARG_TYPE('buffers', 'ArrayBufferView[]', buffers);
616+
}
617+
618+
const req = new FSReqCallback();
619+
req.oncomplete = wrapper;
620+
621+
callback = maybeCallback(callback || position);
622+
623+
if (typeof position !== 'number')
624+
position = null;
625+
626+
return binding.writeBuffers(fd, buffers, position, req);
627+
}
628+
629+
Object.defineProperty(writev, internalUtil.customPromisifyArgs, {
630+
value: ['bytesWritten', 'buffer'],
631+
enumerable: false
632+
});
633+
634+
// fs.writevSync(fd, buffers[, position]);
635+
function writevSync(fd, buffers, position) {
636+
637+
validateUint32(fd, 'fd');
638+
const ctx = {};
639+
640+
if (!isBuffersArray(buffers)) {
641+
throw new ERR_INVALID_ARG_TYPE('buffers', 'ArrayBufferView[]', buffers);
642+
}
643+
644+
if (typeof position !== 'number')
645+
position = null;
646+
647+
const result = binding.writeBuffers(fd, buffers, position, undefined, ctx);
648+
649+
handleErrorFromBinding(ctx);
650+
return result;
651+
}
652+
592653
function rename(oldPath, newPath, callback) {
593654
callback = makeCallback(callback);
594655
oldPath = getValidatedPath(oldPath, 'oldPath');
@@ -1825,6 +1886,8 @@ module.exports = fs = {
18251886
writeFileSync,
18261887
write,
18271888
writeSync,
1889+
writev,
1890+
writevSync,
18281891
Dirent,
18291892
Stats,
18301893

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

+87
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

+92
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

+4-4
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)