GeezerVison is showing github.com — fs: add `bufferSize` option to `fs.opendir()` · nodejs/node@ba5683c
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: benchmark/fs/bench-opendir.js +7-5Lines changed: 7 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,22 +7,24 @@ const path = require('path'); | 7 | 7 | const bench = common.createBenchmark(main, { | 8 | 8 | n: [100], | 9 | 9 | dir: [ 'lib', 'test/parallel'], | 10 | | - mode: [ 'async', 'sync', 'callback' ] | | 10 | + mode: [ 'async', 'sync', 'callback' ], | | 11 | + bufferSize: [ 4, 32, 1024 ] | 11 | 12 | }); | 12 | 13 |
| 13 | | -async function main({ n, dir, mode }) { | | 14 | +async function main({ n, dir, mode, bufferSize }) { | 14 | 15 | const fullPath = path.resolve(__dirname, '../../', dir); | 15 | 16 |
| 16 | 17 | bench.start(); | 17 | 18 |
| 18 | 19 | let counter = 0; | 19 | 20 | for (let i = 0; i < n; i++) { | 20 | 21 | if (mode === 'async') { | | 22 | + const dir = await fs.promises.opendir(fullPath, { bufferSize }); | 21 | 23 | // eslint-disable-next-line no-unused-vars | 22 | | - for await (const entry of await fs.promises.opendir(fullPath)) | | 24 | + for await (const entry of dir) | 23 | 25 | counter++; | 24 | 26 | } else if (mode === 'callback') { | 25 | | - const dir = await fs.promises.opendir(fullPath); | | 27 | + const dir = await fs.promises.opendir(fullPath, { bufferSize }); | 26 | 28 | await new Promise((resolve, reject) => { | 27 | 29 | function read() { | 28 | 30 | dir.read((err, entry) => { | @@ -40,7 +42,7 @@ async function main({ n, dir, mode }) { | 40 | 42 | read(); | 41 | 43 | }); | 42 | 44 | } else { | 43 | | - const dir = fs.opendirSync(fullPath); | | 45 | + const dir = fs.opendirSync(fullPath, { bufferSize }); | 44 | 46 | while (dir.readSync() !== null) | 45 | 47 | counter++; | 46 | 48 | dir.closeSync(); | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/fs.md +21Lines changed: 21 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2625,11 +2625,18 @@ Functions based on `fs.open()` exhibit this behavior as well: | 2625 | 2625 | ## `fs.opendir(path[, options], callback)` | 2626 | 2626 | <!-- YAML | 2627 | 2627 | added: v12.12.0 | | 2628 | +changes: | | 2629 | + - version: REPLACEME | | 2630 | + pr-url: https://github.com/nodejs/node/pull/30114 | | 2631 | + description: The `bufferSize` option was introduced. | 2628 | 2632 | --> | 2629 | 2633 |
| 2630 | 2634 | * `path` {string|Buffer|URL} | 2631 | 2635 | * `options` {Object} | 2632 | 2636 | * `encoding` {string|null} **Default:** `'utf8'` | | 2637 | + * `bufferSize` {number} Number of directory entries that are buffered | | 2638 | + internally when reading from the directory. Higher values lead to better | | 2639 | + performance but higher memory usage. **Default:** `32` | 2633 | 2640 | * `callback` {Function} | 2634 | 2641 | * `err` {Error} | 2635 | 2642 | * `dir` {fs.Dir} | @@ -2645,11 +2652,18 @@ directory and subsequent read operations. | 2645 | 2652 | ## `fs.opendirSync(path[, options])` | 2646 | 2653 | <!-- YAML | 2647 | 2654 | added: v12.12.0 | | 2655 | +changes: | | 2656 | + - version: REPLACEME | | 2657 | + pr-url: https://github.com/nodejs/node/pull/30114 | | 2658 | + description: The `bufferSize` option was introduced. | 2648 | 2659 | --> | 2649 | 2660 |
| 2650 | 2661 | * `path` {string|Buffer|URL} | 2651 | 2662 | * `options` {Object} | 2652 | 2663 | * `encoding` {string|null} **Default:** `'utf8'` | | 2664 | + * `bufferSize` {number} Number of directory entries that are buffered | | 2665 | + internally when reading from the directory. Higher values lead to better | | 2666 | + performance but higher memory usage. **Default:** `32` | 2653 | 2667 | * Returns: {fs.Dir} | 2654 | 2668 |
| 2655 | 2669 | Synchronously open a directory. See opendir(3). | @@ -4829,11 +4843,18 @@ a colon, Node.js will open a file system stream, as described by | 4829 | 4843 | ### `fsPromises.opendir(path[, options])` | 4830 | 4844 | <!-- YAML | 4831 | 4845 | added: v12.12.0 | | 4846 | +changes: | | 4847 | + - version: REPLACEME | | 4848 | + pr-url: https://github.com/nodejs/node/pull/30114 | | 4849 | + description: The `bufferSize` option was introduced. | 4832 | 4850 | --> | 4833 | 4851 |
| 4834 | 4852 | * `path` {string|Buffer|URL} | 4835 | 4853 | * `options` {Object} | 4836 | 4854 | * `encoding` {string|null} **Default:** `'utf8'` | | 4855 | + * `bufferSize` {number} Number of directory entries that are buffered | | 4856 | + internally when reading from the directory. Higher values lead to better | | 4857 | + performance but higher memory usage. **Default:** `32` | 4837 | 4858 | * Returns: {Promise} containing {fs.Dir} | 4838 | 4859 |
| 4839 | 4860 | Asynchronously open a directory. See opendir(3). | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/dir.js +13-3Lines changed: 13 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -21,6 +21,9 @@ const { | 21 | 21 | getValidatedPath, | 22 | 22 | handleErrorFromBinding | 23 | 23 | } = require('internal/fs/utils'); | | 24 | +const { | | 25 | + validateUint32 | | 26 | +} = require('internal/validators'); | 24 | 27 |
| 25 | 28 | const kDirHandle = Symbol('kDirHandle'); | 26 | 29 | const kDirPath = Symbol('kDirPath'); | @@ -39,9 +42,14 @@ class Dir { | 39 | 42 | this[kDirPath] = path; | 40 | 43 | this[kDirClosed] = false; | 41 | 44 |
| 42 | | - this[kDirOptions] = getOptions(options, { | 43 | | - encoding: 'utf8' | 44 | | - }); | | 45 | + this[kDirOptions] = { | | 46 | + bufferSize: 32, | | 47 | + ...getOptions(options, { | | 48 | + encoding: 'utf8' | | 49 | + }) | | 50 | + }; | | 51 | + | | 52 | + validateUint32(this[kDirOptions].bufferSize, 'options.bufferSize', true); | 45 | 53 |
| 46 | 54 | this[kDirReadPromisified] = | 47 | 55 | internalUtil.promisify(this[kDirReadImpl]).bind(this, false); | @@ -88,6 +96,7 @@ class Dir { | 88 | 96 |
| 89 | 97 | this[kDirHandle].read( | 90 | 98 | this[kDirOptions].encoding, | | 99 | + this[kDirOptions].bufferSize, | 91 | 100 | req | 92 | 101 | ); | 93 | 102 | } | @@ -105,6 +114,7 @@ class Dir { | 105 | 114 | const ctx = { path: this[kDirPath] }; | 106 | 115 | const result = this[kDirHandle].read( | 107 | 116 | this[kDirOptions].encoding, | | 117 | + this[kDirOptions].bufferSize, | 108 | 118 | undefined, | 109 | 119 | ctx | 110 | 120 | ); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_dir.cc +18-8Lines changed: 18 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -36,6 +36,7 @@ using v8::Isolate; | 36 | 36 | using v8::Local; | 37 | 37 | using v8::MaybeLocal; | 38 | 38 | using v8::Null; | | 39 | +using v8::Number; | 39 | 40 | using v8::Object; | 40 | 41 | using v8::ObjectTemplate; | 41 | 42 | using v8::String; | @@ -59,8 +60,8 @@ DirHandle::DirHandle(Environment* env, Local<Object> obj, uv_dir_t* dir) | 59 | 60 | dir_(dir) { | 60 | 61 | MakeWeak(); | 61 | 62 |
| 62 | | - dir_->nentries = arraysize(dirents_); | 63 | | - dir_->dirents = dirents_; | | 63 | + dir_->nentries = 0; | | 64 | + dir_->dirents = nullptr; | 64 | 65 | } | 65 | 66 |
| 66 | 67 | DirHandle* DirHandle::New(Environment* env, uv_dir_t* dir) { | @@ -230,22 +231,31 @@ void DirHandle::Read(const FunctionCallbackInfo<Value>& args) { | 230 | 231 | Isolate* isolate = env->isolate(); | 231 | 232 |
| 232 | 233 | const int argc = args.Length(); | 233 | | - CHECK_GE(argc, 2); | | 234 | + CHECK_GE(argc, 3); | 234 | 235 |
| 235 | 236 | const enum encoding encoding = ParseEncoding(isolate, args[0], UTF8); | 236 | 237 |
| 237 | 238 | DirHandle* dir; | 238 | 239 | ASSIGN_OR_RETURN_UNWRAP(&dir, args.Holder()); | 239 | 240 |
| 240 | | - FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); | 241 | | - if (req_wrap_async != nullptr) { // dir.read(encoding, req) | | 241 | + CHECK(args[1]->IsNumber()); | | 242 | + uint64_t buffer_size = args[1].As<Number>()->Value(); | | 243 | + | | 244 | + if (buffer_size != dir->dirents_.size()) { | | 245 | + dir->dirents_.resize(buffer_size); | | 246 | + dir->dir_->nentries = buffer_size; | | 247 | + dir->dir_->dirents = dir->dirents_.data(); | | 248 | + } | | 249 | + | | 250 | + FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); | | 251 | + if (req_wrap_async != nullptr) { // dir.read(encoding, bufferSize, req) | 242 | 252 | AsyncCall(env, req_wrap_async, args, "readdir", encoding, | 243 | 253 | AfterDirRead, uv_fs_readdir, dir->dir()); | 244 | | - } else { // dir.read(encoding, undefined, ctx) | 245 | | - CHECK_EQ(argc, 3); | | 254 | + } else { // dir.read(encoding, bufferSize, undefined, ctx) | | 255 | + CHECK_EQ(argc, 4); | 246 | 256 | FSReqWrapSync req_wrap_sync; | 247 | 257 | FS_DIR_SYNC_TRACE_BEGIN(readdir); | 248 | | - int err = SyncCall(env, args[2], &req_wrap_sync, "readdir", uv_fs_readdir, | | 258 | + int err = SyncCall(env, args[3], &req_wrap_sync, "readdir", uv_fs_readdir, | 249 | 259 | dir->dir()); | 250 | 260 | FS_DIR_SYNC_TRACE_END(readdir); | 251 | 261 | if (err < 0) { | |
Collapse file Copy file name to clipboardExpand all lines: src/node_dir.h +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -45,8 +45,8 @@ class DirHandle : public AsyncWrap { | 45 | 45 | void GCClose(); | 46 | 46 |
| 47 | 47 | uv_dir_t* dir_; | 48 | | - // Up to 32 directory entries are read through a single libuv call. | 49 | | - uv_dirent_t dirents_[32]; | | 48 | + // Multiple entries are read through a single libuv call. | | 49 | + std::vector<uv_dirent_t> dirents_; | 50 | 50 | bool closing_ = false; | 51 | 51 | bool closed_ = false; | 52 | 52 | }; | |
Collapse file Copy file name to clipboardExpand all lines: test/benchmark/test-benchmark-fs.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,6 +7,7 @@ const tmpdir = require('../common/tmpdir'); | 7 | 7 | tmpdir.refresh(); | 8 | 8 |
| 9 | 9 | runBenchmark('fs', [ | | 10 | + 'bufferSize=32', | 10 | 11 | 'concurrent=1', | 11 | 12 | 'dir=.github', | 12 | 13 | 'dur=0.1', | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-fs-opendir.js +23Lines changed: 23 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -186,3 +186,26 @@ async function doAsyncIterThrowTest() { | 186 | 186 | await assert.rejects(async () => dir.read(), dirclosedError); | 187 | 187 | } | 188 | 188 | doAsyncIterThrowTest().then(common.mustCall()); | | 189 | + | | 190 | +// Check error thrown on invalid values of bufferSize | | 191 | +for (const bufferSize of [-1, 0, 0.5, 1.5, Infinity, NaN]) { | | 192 | + assert.throws( | | 193 | + () => fs.opendirSync(testDir, { bufferSize }), | | 194 | + { | | 195 | + code: 'ERR_OUT_OF_RANGE' | | 196 | + }); | | 197 | +} | | 198 | +for (const bufferSize of ['', '1', null]) { | | 199 | + assert.throws( | | 200 | + () => fs.opendirSync(testDir, { bufferSize }), | | 201 | + { | | 202 | + code: 'ERR_INVALID_ARG_TYPE' | | 203 | + }); | | 204 | +} | | 205 | + | | 206 | +// Check that passing a positive integer as bufferSize works | | 207 | +{ | | 208 | + const dir = fs.opendirSync(testDir, { bufferSize: 1024 }); | | 209 | + assertDirent(dir.readSync()); | | 210 | + dir.close(); | | 211 | +} |
|