| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d83e253 commit 92bdfd1
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3031,6 +3031,10 @@ If `options.withFileTypes` is set to `true`, the result will contain | |||
| 3031 | 3031 | <!-- YAML | |
| 3032 | 3032 | added: v0.1.29 | |
| 3033 | 3033 | changes: | |
| 3034 | + - version: REPLACEME | ||
| 3035 | + pr-url: https://github.com/nodejs/node/pull/35911 | ||
| 3036 | + description: The options argument may include an AbortSignal to abort an | ||
| 3037 | + ongoing readFile request. | ||
| 3034 | 3038 | - version: v10.0.0 | |
| 3035 | 3039 | pr-url: https://github.com/nodejs/node/pull/12562 | |
| 3036 | 3040 | description: The `callback` parameter is no longer optional. Not passing | |
@@ -3056,6 +3060,7 @@ changes: | |||
| 3056 | 3060 | * `options` {Object|string} | |
| 3057 | 3061 | * `encoding` {string|null} **Default:** `null` | |
| 3058 | 3062 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`. | |
| 3063 | + * `signal` {AbortSignal} allows aborting an in-progress readFile | ||
| 3059 | 3064 | * `callback` {Function} | |
| 3060 | 3065 | * `err` {Error} | |
| 3061 | 3066 | * `data` {string|Buffer} | |
@@ -3097,9 +3102,25 @@ fs.readFile('<directory>', (err, data) => { | |||
| 3097 | 3102 | }); | |
| 3098 | 3103 | ``` | |
| 3099 | 3104 | ||
| 3105 | + It is possible to abort an ongoing request using an `AbortSignal`. If a | ||
| 3106 | + request is aborted the callback is called with an `AbortError`: | ||
| 3107 | + | ||
| 3108 | + ```js | ||
| 3109 | + const controller = new AbortController(); | ||
| 3110 | + const signal = controller.signal; | ||
| 3111 | + fs.readFile(fileInfo[0].name, { signal }, (err, buf) => { | ||
| 3112 | + // ... | ||
| 3113 | + }); | ||
| 3114 | + // When you want to abort the request | ||
| 3115 | + controller.abort(); | ||
| 3116 | + ``` | ||
| 3117 | + | ||
| 3100 | 3118 | The `fs.readFile()` function buffers the entire file. To minimize memory costs, | |
| 3101 | 3119 | when possible prefer streaming via `fs.createReadStream()`. | |
| 3102 | 3120 | ||
| 3121 | + Aborting an ongoing request does not abort individual operating | ||
| 3122 | + system requests but rather the internal buffering `fs.readFile` performs. | ||
| 3123 | + | ||
| 3103 | 3124 | ### File descriptors | |
| 3104 | 3125 | ||
| 3105 | 3126 | 1. Any specified file descriptor has to support reading. | |
@@ -4771,6 +4792,7 @@ added: v10.0.0 | |||
| 4771 | 4792 | ||
| 4772 | 4793 | * `options` {Object|string} | |
| 4773 | 4794 | * `encoding` {string|null} **Default:** `null` | |
| 4795 | + * `signal` {AbortSignal} allows aborting an in-progress readFile | ||
| 4774 | 4796 | * Returns: {Promise} | |
| 4775 | 4797 | ||
| 4776 | 4798 | Asynchronously reads the entire contents of a file. | |
@@ -5438,12 +5460,18 @@ print('./').catch(console.error); | |||
| 5438 | 5460 | ### `fsPromises.readFile(path[, options])` | |
| 5439 | 5461 | <!-- YAML | |
| 5440 | 5462 | added: v10.0.0 | |
| 5463 | + changes: | ||
| 5464 | + - version: REPLACEME | ||
| 5465 | + pr-url: https://github.com/nodejs/node/pull/35911 | ||
| 5466 | + description: The options argument may include an AbortSignal to abort an | ||
| 5467 | + ongoing readFile request. | ||
| 5441 | 5468 | --> | |
| 5442 | 5469 | ||
| 5443 | 5470 | * `path` {string|Buffer|URL|FileHandle} filename or `FileHandle` | |
| 5444 | 5471 | * `options` {Object|string} | |
| 5445 | 5472 | * `encoding` {string|null} **Default:** `null` | |
| 5446 | 5473 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`. | |
| 5474 | + * `signal` {AbortSignal} allows aborting an in-progress readFile | ||
| 5447 | 5475 | * Returns: {Promise} | |
| 5448 | 5476 | ||
| 5449 | 5477 | Asynchronously reads the entire contents of a file. | |
@@ -5459,6 +5487,20 @@ platform-specific. On macOS, Linux, and Windows, the promise will be rejected | |||
| 5459 | 5487 | with an error. On FreeBSD, a representation of the directory's contents will be | |
| 5460 | 5488 | returned. | |
| 5461 | 5489 | ||
| 5490 | + It is possible to abort an ongoing `readFile` using an `AbortSignal`. If a | ||
| 5491 | + request is aborted the promise returned is rejected with an `AbortError`: | ||
| 5492 | + | ||
| 5493 | + ```js | ||
| 5494 | + const controller = new AbortController(); | ||
| 5495 | + const signal = controller.signal; | ||
| 5496 | + readFile(fileName, { signal }).then((file) => { /* ... */ }); | ||
| 5497 | + // Abort the request | ||
| 5498 | + controller.abort(); | ||
| 5499 | + ``` | ||
| 5500 | + | ||
| 5501 | + Aborting an ongoing request does not abort individual operating | ||
| 5502 | + system requests but rather the internal buffering `fs.readFile` performs. | ||
| 5503 | + | ||
| 5462 | 5504 | Any specified `FileHandle` has to support reading. | |
| 5463 | 5505 | ||
| 5464 | 5506 | ### `fsPromises.readlink(path[, options])` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -315,6 +315,9 @@ function readFile(path, options, callback) { | |||
| 315 | 315 | const context = new ReadFileContext(callback, options.encoding); | |
| 316 | 316 | context.isUserFd = isFd(path); // File descriptor ownership | |
| 317 | 317 | ||
| 318 | + if (options.signal) { | ||
| 319 | + context.signal = options.signal; | ||
| 320 | + } | ||
| 318 | 321 | if (context.isUserFd) { | |
| 319 | 322 | process.nextTick(function tick(context) { | |
| 320 | 323 | readFileAfterOpen.call({ context }, null, path); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,12 +29,14 @@ const { | |||
| 29 | 29 | } = internalBinding('constants').fs; | |
| 30 | 30 | const binding = internalBinding('fs'); | |
| 31 | 31 | const { Buffer } = require('buffer'); | |
| 32 | + | ||
| 33 | + const { codes, hideStackFrames } = require('internal/errors'); | ||
| 32 | 34 | const { | |
| 33 | 35 | ERR_FS_FILE_TOO_LARGE, | |
| 34 | 36 | ERR_INVALID_ARG_TYPE, | |
| 35 | 37 | ERR_INVALID_ARG_VALUE, | |
| 36 | - ERR_METHOD_NOT_IMPLEMENTED | ||
| 37 | - } = require('internal/errors').codes; | ||
| 38 | + ERR_METHOD_NOT_IMPLEMENTED, | ||
| 39 | + } = codes; | ||
| 38 | 40 | const { isArrayBufferView } = require('internal/util/types'); | |
| 39 | 41 | const { rimrafPromises } = require('internal/fs/rimraf'); | |
| 40 | 42 | const { | |
@@ -82,6 +84,13 @@ const { | |||
| 82 | 84 | const getDirectoryEntriesPromise = promisify(getDirents); | |
| 83 | 85 | const validateRmOptionsPromise = promisify(validateRmOptions); | |
| 84 | 86 | ||
| 87 | + let DOMException; | ||
| 88 | + const lazyDOMException = hideStackFrames((message, name) => { | ||
| 89 | + if (DOMException === undefined) | ||
| 90 | + DOMException = internalBinding('messaging').DOMException; | ||
| 91 | + return new DOMException(message, name); | ||
| 92 | + }); | ||
| 93 | + | ||
| 85 | 94 | class FileHandle extends JSTransferable { | |
| 86 | 95 | constructor(filehandle) { | |
| 87 | 96 | super(); | |
@@ -259,8 +268,17 @@ async function writeFileHandle(filehandle, data) { | |||
| 259 | 268 | } | |
| 260 | 269 | ||
| 261 | 270 | async function readFileHandle(filehandle, options) { | |
| 271 | + const signal = options && options.signal; | ||
| 272 | + | ||
| 273 | + if (signal && signal.aborted) { | ||
| 274 | + throw lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 275 | + } | ||
| 262 | 276 | const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); | |
| 263 | 277 | ||
| 278 | + if (signal && signal.aborted) { | ||
| 279 | + throw lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 280 | + } | ||
| 281 | + | ||
| 264 | 282 | let size; | |
| 265 | 283 | if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) { | |
| 266 | 284 | size = statFields[8/* size */]; | |
@@ -277,6 +295,9 @@ async function readFileHandle(filehandle, options) { | |||
| 277 | 295 | MathMin(size, kReadFileMaxChunkSize); | |
| 278 | 296 | let endOfFile = false; | |
| 279 | 297 | do { | |
| 298 | + if (signal && signal.aborted) { | ||
| 299 | + throw lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 300 | + } | ||
| 280 | 301 | const buf = Buffer.alloc(chunkSize); | |
| 281 | 302 | const { bytesRead, buffer } = | |
| 282 | 303 | await read(filehandle, buf, 0, chunkSize, -1); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,16 @@ const { Buffer } = require('buffer'); | |||
| 8 | 8 | ||
| 9 | 9 | const { FSReqCallback, close, read } = internalBinding('fs'); | |
| 10 | 10 | ||
| 11 | + const { hideStackFrames } = require('internal/errors'); | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + let DOMException; | ||
| 15 | + const lazyDOMException = hideStackFrames((message, name) => { | ||
| 16 | + if (DOMException === undefined) | ||
| 17 | + DOMException = internalBinding('messaging').DOMException; | ||
| 18 | + return new DOMException(message, name); | ||
| 19 | + }); | ||
| 20 | + | ||
| 11 | 21 | // Use 64kb in case the file type is not a regular file and thus do not know the | |
| 12 | 22 | // actual file size. Increasing the value further results in more frequent over | |
| 13 | 23 | // allocation for small files and consumes CPU time and memory that should be | |
@@ -74,13 +84,19 @@ class ReadFileContext { | |||
| 74 | 84 | this.pos = 0; | |
| 75 | 85 | this.encoding = encoding; | |
| 76 | 86 | this.err = null; | |
| 87 | + this.signal = undefined; | ||
| 77 | 88 | } | |
| 78 | 89 | ||
| 79 | 90 | read() { | |
| 80 | 91 | let buffer; | |
| 81 | 92 | let offset; | |
| 82 | 93 | let length; | |
| 83 | 94 | ||
| 95 | + if (this.signal && this.signal.aborted) { | ||
| 96 | + return this.close( | ||
| 97 | + lazyDOMException('The operation was aborted', 'AbortError') | ||
| 98 | + ); | ||
| 99 | + } | ||
| 84 | 100 | if (this.size === 0) { | |
| 85 | 101 | buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength); | |
| 86 | 102 | offset = 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,6 +35,7 @@ const { | |||
| 35 | 35 | const { once } = require('internal/util'); | |
| 36 | 36 | const { toPathIfFileURL } = require('internal/url'); | |
| 37 | 37 | const { | |
| 38 | + validateAbortSignal, | ||
| 38 | 39 | validateBoolean, | |
| 39 | 40 | validateInt32, | |
| 40 | 41 | validateUint32 | |
@@ -296,6 +297,10 @@ function getOptions(options, defaultOptions) { | |||
| 296 | 297 | ||
| 297 | 298 | if (options.encoding !== 'buffer') | |
| 298 | 299 | assertEncoding(options.encoding); | |
| 300 | + | ||
| 301 | + if (options.signal !== undefined) { | ||
| 302 | + validateAbortSignal(options.signal, 'options.signal'); | ||
| 303 | + } | ||
| 299 | 304 | return options; | |
| 300 | 305 | } | |
| 301 | 306 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,18 +10,21 @@ tmpdir.refresh(); | |||
| 10 | 10 | ||
| 11 | 11 | const fn = path.join(tmpdir.path, 'large-file'); | |
| 12 | 12 | ||
| 13 | - async function validateReadFile() { | ||
| 14 | - // Creating large buffer with random content | ||
| 15 | - const buffer = Buffer.from( | ||
| 16 | - Array.apply(null, { length: 16834 * 2 }) | ||
| 17 | - .map(Math.random) | ||
| 18 | - .map((number) => (number * (1 << 8))) | ||
| 19 | - ); | ||
| 13 | + // Creating large buffer with random content | ||
| 14 | + const largeBuffer = Buffer.from( | ||
| 15 | + Array.apply(null, { length: 16834 * 2 }) | ||
| 16 | + .map(Math.random) | ||
| 17 | + .map((number) => (number * (1 << 8))) | ||
| 18 | + ); | ||
| 20 | 19 | ||
| 20 | + async function createLargeFile() { | ||
| 21 | 21 | // Writing buffer to a file then try to read it | |
| 22 | - await writeFile(fn, buffer); | ||
| 22 | + await writeFile(fn, largeBuffer); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + async function validateReadFile() { | ||
| 23 | 26 | const readBuffer = await readFile(fn); | |
| 24 | - assert.strictEqual(readBuffer.equals(buffer), true); | ||
| 27 | + assert.strictEqual(readBuffer.equals(largeBuffer), true); | ||
| 25 | 28 | } | |
| 26 | 29 | ||
| 27 | 30 | async function validateReadFileProc() { | |
@@ -39,6 +42,28 @@ async function validateReadFileProc() { | |||
| 39 | 42 | assert.ok(hostname.length > 0); | |
| 40 | 43 | } | |
| 41 | 44 | ||
| 42 | - validateReadFile() | ||
| 43 | - .then(() => validateReadFileProc()) | ||
| 44 | - .then(common.mustCall()); | ||
| 45 | + function validateReadFileAbortLogicBefore() { | ||
| 46 | + const controller = new AbortController(); | ||
| 47 | + const signal = controller.signal; | ||
| 48 | + controller.abort(); | ||
| 49 | + assert.rejects(readFile(fn, { signal }), { | ||
| 50 | + name: 'AbortError' | ||
| 51 | + }); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + function validateReadFileAbortLogicDuring() { | ||
| 55 | + const controller = new AbortController(); | ||
| 56 | + const signal = controller.signal; | ||
| 57 | + process.nextTick(() => controller.abort()); | ||
| 58 | + assert.rejects(readFile(fn, { signal }), { | ||
| 59 | + name: 'AbortError' | ||
| 60 | + }); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + (async () => { | ||
| 64 | + await createLargeFile(); | ||
| 65 | + await validateReadFile(); | ||
| 66 | + await validateReadFileProc(); | ||
| 67 | + await validateReadFileAbortLogicBefore(); | ||
| 68 | + await validateReadFileAbortLogicDuring(); | ||
| 69 | + })().then(common.mustCall()); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -57,3 +57,21 @@ for (const e of fileInfo) { | |||
| 57 | 57 | assert.deepStrictEqual(buf, e.contents); | |
| 58 | 58 | })); | |
| 59 | 59 | } | |
| 60 | + { | ||
| 61 | + // Test cancellation, before | ||
| 62 | + const controller = new AbortController(); | ||
| 63 | + const signal = controller.signal; | ||
| 64 | + controller.abort(); | ||
| 65 | + fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => { | ||
| 66 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 67 | + })); | ||
| 68 | + } | ||
| 69 | + { | ||
| 70 | + // Test cancellation, during read | ||
| 71 | + const controller = new AbortController(); | ||
| 72 | + const signal = controller.signal; | ||
| 73 | + fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => { | ||
| 74 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 75 | + })); | ||
| 76 | + process.nextTick(() => controller.abort()); | ||
| 77 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments