| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7e34aa4 commit 839964e
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1365,6 +1365,13 @@ Path is a directory. | |||
| 1365 | 1365 | An attempt has been made to read a file whose size is larger than the maximum | |
| 1366 | 1366 | allowed size for a `Buffer`. | |
| 1367 | 1367 | ||
| 1368 | + <a id="ERR_FS_WATCH_QUEUE_OVERFLOW"></a> | ||
| 1369 | + | ||
| 1370 | + ### `ERR_FS_WATCH_QUEUE_OVERFLOW` | ||
| 1371 | + | ||
| 1372 | + The number of file system events queued without being handled exceeded the size specified in | ||
| 1373 | + `maxQueue` in `fs.watch()`. | ||
| 1374 | + | ||
| 1368 | 1375 | <a id="ERR_HTTP2_ALTSVC_INVALID_ORIGIN"></a> | |
| 1369 | 1376 | ||
| 1370 | 1377 | ### `ERR_HTTP2_ALTSVC_INVALID_ORIGIN` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1797,6 +1797,11 @@ added: | |||
| 1797 | 1797 | filename passed to the listener. **Default:** `'utf8'`. | |
| 1798 | 1798 | * `signal` {AbortSignal} An {AbortSignal} used to signal when the watcher | |
| 1799 | 1799 | should stop. | |
| 1800 | + * `maxQueue` {number} Specifies the number of events to queue between iterations | ||
| 1801 | + of the {AsyncIterator} returned. **Default:** `2048`. | ||
| 1802 | + * `overflow` {string} Either `'ignore'` or `'throw'` when there are more events to be | ||
| 1803 | + queued than `maxQueue` allows. `'ignore'` means overflow events are dropped and a | ||
| 1804 | + warning is emitted, while `'throw'` means to throw an exception. **Default:** `'ignore'`. | ||
| 1800 | 1805 | * Returns: {AsyncIterator} of objects with the properties: | |
| 1801 | 1806 | * `eventType` {string} The type of change | |
| 1802 | 1807 | * `filename` {string|Buffer|null} The name of the file changed. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1219,6 +1219,7 @@ E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY', | |||
| 1219 | 1219 | E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type', SystemError); | |
| 1220 | 1220 | E('ERR_FS_EISDIR', 'Path is a directory', SystemError, HideStackFramesError); | |
| 1221 | 1221 | E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GiB', RangeError); | |
| 1222 | + E('ERR_FS_WATCH_QUEUE_OVERFLOW', 'fs.watch() queued more than %d events', Error); | ||
| 1222 | 1223 | E('ERR_HTTP2_ALTSVC_INVALID_ORIGIN', | |
| 1223 | 1224 | 'HTTP/2 ALTSVC frames require a valid origin', TypeError); | |
| 1224 | 1225 | E('ERR_HTTP2_ALTSVC_LENGTH', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,9 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + ArrayPrototypePush, | ||
| 5 | + ArrayPrototypeShift, | ||
| 6 | + Error, | ||
| 4 | 7 | FunctionPrototypeCall, | |
| 5 | 8 | ObjectDefineProperty, | |
| 6 | 9 | ObjectSetPrototypeOf, | |
@@ -12,9 +15,11 @@ const { | |||
| 12 | 15 | AbortError, | |
| 13 | 16 | UVException, | |
| 14 | 17 | codes: { | |
| 18 | + ERR_FS_WATCH_QUEUE_OVERFLOW, | ||
| 15 | 19 | ERR_INVALID_ARG_VALUE, | |
| 16 | 20 | }, | |
| 17 | 21 | } = require('internal/errors'); | |
| 22 | + | ||
| 18 | 23 | const { | |
| 19 | 24 | kEmptyObject, | |
| 20 | 25 | } = require('internal/util'); | |
@@ -45,6 +50,8 @@ const { | |||
| 45 | 50 | validateBoolean, | |
| 46 | 51 | validateObject, | |
| 47 | 52 | validateUint32, | |
| 53 | + validateInteger, | ||
| 54 | + validateOneOf, | ||
| 48 | 55 | } = require('internal/validators'); | |
| 49 | 56 | ||
| 50 | 57 | const { | |
@@ -309,11 +316,15 @@ async function* watch(filename, options = kEmptyObject) { | |||
| 309 | 316 | persistent = true, | |
| 310 | 317 | recursive = false, | |
| 311 | 318 | encoding = 'utf8', | |
| 319 | + maxQueue = 2048, | ||
| 320 | + overflow = 'ignore', | ||
| 312 | 321 | signal, | |
| 313 | 322 | } = options; | |
| 314 | 323 | ||
| 315 | 324 | validateBoolean(persistent, 'options.persistent'); | |
| 316 | 325 | validateBoolean(recursive, 'options.recursive'); | |
| 326 | + validateInteger(maxQueue, 'options.maxQueue'); | ||
| 327 | + validateOneOf(overflow, 'options.overflow', ['ignore', 'error']); | ||
| 317 | 328 | validateAbortSignal(signal, 'options.signal'); | |
| 318 | 329 | ||
| 319 | 330 | if (encoding && !isEncoding(encoding)) { | |
@@ -325,10 +336,11 @@ async function* watch(filename, options = kEmptyObject) { | |||
| 325 | 336 | throw new AbortError(undefined, { cause: signal.reason }); | |
| 326 | 337 | ||
| 327 | 338 | const handle = new FSEvent(); | |
| 328 | - let { promise, resolve, reject } = PromiseWithResolvers(); | ||
| 339 | + let { promise, resolve } = PromiseWithResolvers(); | ||
| 340 | + const queue = []; | ||
| 329 | 341 | const oncancel = () => { | |
| 330 | 342 | handle.close(); | |
| 331 | - reject(new AbortError(undefined, { cause: signal?.reason })); | ||
| 343 | + resolve(); | ||
| 332 | 344 | }; | |
| 333 | 345 | ||
| 334 | 346 | try { | |
@@ -345,11 +357,20 @@ async function* watch(filename, options = kEmptyObject) { | |||
| 345 | 357 | }); | |
| 346 | 358 | error.filename = filename; | |
| 347 | 359 | handle.close(); | |
| 348 | - reject(error); | ||
| 360 | + ArrayPrototypePush(queue, error); | ||
| 361 | + resolve(); | ||
| 349 | 362 | return; | |
| 350 | 363 | } | |
| 351 | - | ||
| 352 | - resolve({ eventType, filename }); | ||
| 364 | + if (queue.length < maxQueue) { | ||
| 365 | + ArrayPrototypePush(queue, { __proto__: null, eventType, filename }); | ||
| 366 | + resolve(); | ||
| 367 | + } else if (overflow === 'error') { | ||
| 368 | + queue.length = 0; | ||
| 369 | + ArrayPrototypePush(queue, new ERR_FS_WATCH_QUEUE_OVERFLOW(maxQueue)); | ||
| 370 | + resolve(); | ||
| 371 | + } else { | ||
| 372 | + process.emitWarning('fs.watch maxQueue exceeded'); | ||
| 373 | + } | ||
| 353 | 374 | }; | |
| 354 | 375 | ||
| 355 | 376 | const err = handle.start(path, persistent, recursive, encoding); | |
@@ -367,10 +388,20 @@ async function* watch(filename, options = kEmptyObject) { | |||
| 367 | 388 | } | |
| 368 | 389 | ||
| 369 | 390 | while (!signal?.aborted) { | |
| 370 | - yield await promise; | ||
| 371 | - ({ promise, resolve, reject } = PromiseWithResolvers()); | ||
| 391 | + await promise; | ||
| 392 | + while (queue.length) { | ||
| 393 | + const item = ArrayPrototypeShift(queue); | ||
| 394 | + if (item instanceof Error) { | ||
| 395 | + throw item; | ||
| 396 | + } else { | ||
| 397 | + yield item; | ||
| 398 | + } | ||
| 399 | + } | ||
| 400 | + ({ promise, resolve } = PromiseWithResolvers()); | ||
| 401 | + } | ||
| 402 | + if (signal?.aborted) { | ||
| 403 | + throw new AbortError(undefined, { cause: signal?.reason }); | ||
| 372 | 404 | } | |
| 373 | - throw new AbortError(undefined, { cause: signal?.reason }); | ||
| 374 | 405 | } finally { | |
| 375 | 406 | handle.close(); | |
| 376 | 407 | signal?.removeEventListener('abort', oncancel); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,7 @@ test-domain-throw-error-then-throw-from-uncaught-exception-handler: PASS, FLAKY | |||
| 77 | 77 | test-domain-with-abort-on-uncaught-exception: PASS, FLAKY | |
| 78 | 78 | # https://github.com/nodejs/node/issues/54346 | |
| 79 | 79 | test-esm-loader-hooks-inspect-wait: PASS, FLAKY | |
| 80 | + test-fs-promises-watch-iterator: SKIP | ||
| 80 | 81 | # https://github.com/nodejs/node/issues/50050 | |
| 81 | 82 | test-tick-processor-arguments: SKIP | |
| 82 | 83 | # https://github.com/nodejs/node/issues/54534 | |
@@ -85,6 +86,7 @@ test-runner-run-watch: PASS, FLAKY | |||
| 85 | 86 | [$system==freebsd] | |
| 86 | 87 | # https://github.com/nodejs/node/issues/54346 | |
| 87 | 88 | test-esm-loader-hooks-inspect-wait: PASS, FLAKY | |
| 89 | + test-fs-promises-watch-iterator: SKIP | ||
| 88 | 90 | ||
| 89 | 91 | [$system==aix] | |
| 90 | 92 | # https://github.com/nodejs/node/issues/54346 | |
@@ -95,6 +97,7 @@ test-esm-loader-hooks-inspect-wait: PASS, FLAKY | |||
| 95 | 97 | test-child-process-fork-net-server: SKIP | |
| 96 | 98 | test-cli-node-options: SKIP | |
| 97 | 99 | test-cluster-shared-leak: SKIP | |
| 100 | + test-fs-promises-watch-iterator: SKIP | ||
| 98 | 101 | test-http-writable-true-after-close: SKIP | |
| 99 | 102 | test-http2-connect-method: SKIP | |
| 100 | 103 | test-net-error-twice: SKIP | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,57 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + // This tests that when there is a burst of fs watch events, the events | ||
| 3 | + // emitted after the consumer receives the initial event and before the | ||
| 4 | + // control returns back to fs.watch() can be queued up and show up | ||
| 5 | + // in the next iteration. | ||
| 6 | + const common = require('../common'); | ||
| 7 | + const { watch, writeFile } = require('fs/promises'); | ||
| 8 | + const fs = require('fs'); | ||
| 9 | + const assert = require('assert'); | ||
| 10 | + const { join } = require('path'); | ||
| 11 | + const { setTimeout } = require('timers/promises'); | ||
| 12 | + const tmpdir = require('../common/tmpdir'); | ||
| 13 | + | ||
| 14 | + class WatchTestCase { | ||
| 15 | + constructor(dirName, files) { | ||
| 16 | + this.dirName = dirName; | ||
| 17 | + this.files = files; | ||
| 18 | + } | ||
| 19 | + get dirPath() { return tmpdir.resolve(this.dirName); } | ||
| 20 | + filePath(fileName) { return join(this.dirPath, fileName); } | ||
| 21 | + | ||
| 22 | + async run() { | ||
| 23 | + await Promise.all([this.watchFiles(), this.writeFiles()]); | ||
| 24 | + assert(!this.files.length); | ||
| 25 | + } | ||
| 26 | + async watchFiles() { | ||
| 27 | + const watcher = watch(this.dirPath); | ||
| 28 | + for await (const evt of watcher) { | ||
| 29 | + const idx = this.files.indexOf(evt.filename); | ||
| 30 | + if (idx < 0) continue; | ||
| 31 | + this.files.splice(idx, 1); | ||
| 32 | + await setTimeout(common.platformTimeout(100)); | ||
| 33 | + if (!this.files.length) break; | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + async writeFiles() { | ||
| 37 | + for (const fileName of [...this.files]) { | ||
| 38 | + await writeFile(this.filePath(fileName), Date.now() + fileName.repeat(1e4)); | ||
| 39 | + } | ||
| 40 | + await setTimeout(common.platformTimeout(100)); | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + const kCases = [ | ||
| 45 | + // Watch on a directory should callback with a filename on supported systems | ||
| 46 | + new WatchTestCase( | ||
| 47 | + 'watch1', | ||
| 48 | + ['foo', 'bar', 'baz'] | ||
| 49 | + ), | ||
| 50 | + ]; | ||
| 51 | + | ||
| 52 | + tmpdir.refresh(); | ||
| 53 | + | ||
| 54 | + for (const testCase of kCases) { | ||
| 55 | + fs.mkdirSync(testCase.dirPath); | ||
| 56 | + testCase.run().then(common.mustCall()); | ||
| 57 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -123,6 +123,25 @@ assert.rejects( | |||
| 123 | 123 | }, | |
| 124 | 124 | { code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall()); | |
| 125 | 125 | ||
| 126 | + assert.rejects( | ||
| 127 | + async () => { | ||
| 128 | + // eslint-disable-next-line no-unused-vars, no-empty | ||
| 129 | + for await (const _ of watch('', { maxQueue: 'silly' })) { } | ||
| 130 | + }, | ||
| 131 | + { code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall()); | ||
| 132 | + assert.rejects( | ||
| 133 | + async () => { | ||
| 134 | + // eslint-disable-next-line no-unused-vars, no-empty | ||
| 135 | + for await (const _ of watch('', { overflow: 1 })) { } | ||
| 136 | + }, | ||
| 137 | + { code: 'ERR_INVALID_ARG_VALUE' }).then(common.mustCall()); | ||
| 138 | + assert.rejects( | ||
| 139 | + async () => { | ||
| 140 | + // eslint-disable-next-line no-unused-vars, no-empty | ||
| 141 | + for await (const _ of watch('', { overflow: 'barf' })) { } | ||
| 142 | + }, | ||
| 143 | + { code: 'ERR_INVALID_ARG_VALUE' }).then(common.mustCall()); | ||
| 144 | + | ||
| 126 | 145 | (async () => { | |
| 127 | 146 | const ac = new AbortController(); | |
| 128 | 147 | const { signal } = ac; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments