| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 754b7a7 commit 6033d30
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1751,6 +1751,10 @@ fs.copyFileSync('source.txt', 'destination.txt', COPYFILE_EXCL); | |||
| 1751 | 1751 | <!-- YAML | |
| 1752 | 1752 | added: v0.1.31 | |
| 1753 | 1753 | changes: | |
| 1754 | + - version: | ||
| 1755 | + - REPLACEME | ||
| 1756 | + pr-url: https://github.com/nodejs/node/pull/35922 | ||
| 1757 | + description: The `fd` option accepts FileHandle arguments. | ||
| 1754 | 1758 | - version: | |
| 1755 | 1759 | - v13.6.0 | |
| 1756 | 1760 | - v12.17.0 | |
@@ -1782,7 +1786,7 @@ changes: | |||
| 1782 | 1786 | * `flags` {string} See [support of file system `flags`][]. **Default:** | |
| 1783 | 1787 | `'r'`. | |
| 1784 | 1788 | * `encoding` {string} **Default:** `null` | |
| 1785 | - * `fd` {integer} **Default:** `null` | ||
| 1789 | + * `fd` {integer|FileHandle} **Default:** `null` | ||
| 1786 | 1790 | * `mode` {integer} **Default:** `0o666` | |
| 1787 | 1791 | * `autoClose` {boolean} **Default:** `true` | |
| 1788 | 1792 | * `emitClose` {boolean} **Default:** `false` | |
@@ -1858,6 +1862,10 @@ If `options` is a string, then it specifies the encoding. | |||
| 1858 | 1862 | <!-- YAML | |
| 1859 | 1863 | added: v0.1.31 | |
| 1860 | 1864 | changes: | |
| 1865 | + - version: | ||
| 1866 | + - REPLACEME | ||
| 1867 | + pr-url: https://github.com/nodejs/node/pull/35922 | ||
| 1868 | + description: The `fd` option accepts FileHandle arguments. | ||
| 1861 | 1869 | - version: | |
| 1862 | 1870 | - v13.6.0 | |
| 1863 | 1871 | - v12.17.0 | |
@@ -1887,7 +1895,7 @@ changes: | |||
| 1887 | 1895 | * `flags` {string} See [support of file system `flags`][]. **Default:** | |
| 1888 | 1896 | `'w'`. | |
| 1889 | 1897 | * `encoding` {string} **Default:** `'utf8'` | |
| 1890 | - * `fd` {integer} **Default:** `null` | ||
| 1898 | + * `fd` {integer|FileHandle} **Default:** `null` | ||
| 1891 | 1899 | * `mode` {integer} **Default:** `0o666` | |
| 1892 | 1900 | * `autoClose` {boolean} **Default:** `true` | |
| 1893 | 1901 | * `emitClose` {boolean} **Default:** `false` | |
@@ -4697,6 +4705,14 @@ the promise-based API uses the `FileHandle` class in order to help avoid | |||
| 4697 | 4705 | accidental leaking of unclosed file descriptors after a `Promise` is resolved or | |
| 4698 | 4706 | rejected. | |
| 4699 | 4707 | ||
| 4708 | + #### Event: `'close'` | ||
| 4709 | + <!-- YAML | ||
| 4710 | + added: REPLACEME | ||
| 4711 | + --> | ||
| 4712 | + | ||
| 4713 | + The `'close'` event is emitted when the `FileHandle` and any of its underlying | ||
| 4714 | + resources (a file descriptor, for example) have been closed. | ||
| 4715 | + | ||
| 4700 | 4716 | #### `filehandle.appendFile(data, options)` | |
| 4701 | 4717 | <!-- YAML | |
| 4702 | 4718 | added: v10.0.0 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,11 +4,13 @@ const { | |||
| 4 | 4 | ArrayFrom, | |
| 5 | 5 | Boolean, | |
| 6 | 6 | Error, | |
| 7 | + FunctionPrototypeCall, | ||
| 7 | 8 | NumberIsInteger, | |
| 8 | 9 | ObjectAssign, | |
| 9 | 10 | ObjectDefineProperties, | |
| 10 | 11 | ObjectDefineProperty, | |
| 11 | 12 | ObjectGetOwnPropertyDescriptor, | |
| 13 | + ObjectGetOwnPropertyDescriptors, | ||
| 12 | 14 | ReflectApply, | |
| 13 | 15 | SafeMap, | |
| 14 | 16 | String, | |
@@ -646,8 +648,23 @@ function defineEventHandler(emitter, name) { | |||
| 646 | 648 | enumerable: true | |
| 647 | 649 | }); | |
| 648 | 650 | } | |
| 651 | + | ||
| 652 | + const EventEmitterMixin = (Superclass) => { | ||
| 653 | + class MixedEventEmitter extends Superclass { | ||
| 654 | + constructor(...args) { | ||
| 655 | + super(...args); | ||
| 656 | + FunctionPrototypeCall(EventEmitter, this); | ||
| 657 | + } | ||
| 658 | + } | ||
| 659 | + const protoProps = ObjectGetOwnPropertyDescriptors(EventEmitter.prototype); | ||
| 660 | + delete protoProps.constructor; | ||
| 661 | + ObjectDefineProperties(MixedEventEmitter.prototype, protoProps); | ||
| 662 | + return MixedEventEmitter; | ||
| 663 | + }; | ||
| 664 | + | ||
| 649 | 665 | module.exports = { | |
| 650 | 666 | Event, | |
| 667 | + EventEmitterMixin, | ||
| 651 | 668 | EventTarget, | |
| 652 | 669 | NodeEventTarget, | |
| 653 | 670 | defineEventHandler, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -71,13 +71,16 @@ const { | |||
| 71 | 71 | } = require('internal/validators'); | |
| 72 | 72 | const pathModule = require('path'); | |
| 73 | 73 | const { promisify } = require('internal/util'); | |
| 74 | + const { EventEmitterMixin } = require('internal/event_target'); | ||
| 74 | 75 | ||
| 75 | 76 | const kHandle = Symbol('kHandle'); | |
| 76 | 77 | const kFd = Symbol('kFd'); | |
| 77 | 78 | const kRefs = Symbol('kRefs'); | |
| 78 | 79 | const kClosePromise = Symbol('kClosePromise'); | |
| 79 | 80 | const kCloseResolve = Symbol('kCloseResolve'); | |
| 80 | 81 | const kCloseReject = Symbol('kCloseReject'); | |
| 82 | + const kRef = Symbol('kRef'); | ||
| 83 | + const kUnref = Symbol('kUnref'); | ||
| 81 | 84 | ||
| 82 | 85 | const { kUsePromises } = binding; | |
| 83 | 86 | const { | |
@@ -94,7 +97,7 @@ const lazyDOMException = hideStackFrames((message, name) => { | |||
| 94 | 97 | return new DOMException(message, name); | |
| 95 | 98 | }); | |
| 96 | 99 | ||
| 97 | - class FileHandle extends JSTransferable { | ||
| 100 | + class FileHandle extends EventEmitterMixin(JSTransferable) { | ||
| 98 | 101 | constructor(filehandle) { | |
| 99 | 102 | super(); | |
| 100 | 103 | this[kHandle] = filehandle; | |
@@ -197,6 +200,7 @@ class FileHandle extends JSTransferable { | |||
| 197 | 200 | ); | |
| 198 | 201 | } | |
| 199 | 202 | ||
| 203 | + this.emit('close'); | ||
| 200 | 204 | return this[kClosePromise]; | |
| 201 | 205 | } | |
| 202 | 206 | ||
@@ -226,6 +230,22 @@ class FileHandle extends JSTransferable { | |||
| 226 | 230 | this[kHandle] = handle; | |
| 227 | 231 | this[kFd] = handle.fd; | |
| 228 | 232 | } | |
| 233 | + | ||
| 234 | + [kRef]() { | ||
| 235 | + this[kRefs]++; | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + [kUnref]() { | ||
| 239 | + this[kRefs]--; | ||
| 240 | + if (this[kRefs] === 0) { | ||
| 241 | + this[kFd] = -1; | ||
| 242 | + PromisePrototypeThen( | ||
| 243 | + this[kHandle].close(), | ||
| 244 | + this[kCloseResolve], | ||
| 245 | + this[kCloseReject] | ||
| 246 | + ); | ||
| 247 | + } | ||
| 248 | + } | ||
| 229 | 249 | } | |
| 230 | 250 | ||
| 231 | 251 | async function fsCall(fn, handle, ...args) { | |
@@ -242,18 +262,10 @@ async function fsCall(fn, handle, ...args) { | |||
| 242 | 262 | } | |
| 243 | 263 | ||
| 244 | 264 | try { | |
| 245 | - handle[kRefs]++; | ||
| 265 | + handle[kRef](); | ||
| 246 | 266 | return await fn(handle, ...args); | |
| 247 | 267 | } finally { | |
| 248 | - handle[kRefs]--; | ||
| 249 | - if (handle[kRefs] === 0) { | ||
| 250 | - handle[kFd] = -1; | ||
| 251 | - PromisePrototypeThen( | ||
| 252 | - handle[kHandle].close(), | ||
| 253 | - handle[kCloseResolve], | ||
| 254 | - handle[kCloseReject] | ||
| 255 | - ); | ||
| 256 | - } | ||
| 268 | + handle[kUnref](); | ||
| 257 | 269 | } | |
| 258 | 270 | } | |
| 259 | 271 | ||
@@ -712,5 +724,7 @@ module.exports = { | |||
| 712 | 724 | readFile, | |
| 713 | 725 | }, | |
| 714 | 726 | ||
| 715 | - FileHandle | ||
| 727 | + FileHandle, | ||
| 728 | + kRef, | ||
| 729 | + kUnref, | ||
| 716 | 730 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,21 +2,25 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | Array, | |
| 5 | + FunctionPrototypeBind, | ||
| 5 | 6 | MathMin, | |
| 6 | 7 | ObjectDefineProperty, | |
| 7 | 8 | ObjectSetPrototypeOf, | |
| 9 | + PromisePrototypeThen, | ||
| 8 | 10 | ReflectApply, | |
| 9 | 11 | Symbol, | |
| 10 | 12 | } = primordials; | |
| 11 | 13 | ||
| 12 | 14 | const { | |
| 13 | 15 | ERR_INVALID_ARG_TYPE, | |
| 14 | - ERR_OUT_OF_RANGE | ||
| 16 | + ERR_OUT_OF_RANGE, | ||
| 17 | + ERR_METHOD_NOT_IMPLEMENTED, | ||
| 15 | 18 | } = require('internal/errors').codes; | |
| 16 | 19 | const { deprecate } = require('internal/util'); | |
| 17 | 20 | const { validateInteger } = require('internal/validators'); | |
| 18 | 21 | const { errorOrDestroy } = require('internal/streams/destroy'); | |
| 19 | 22 | const fs = require('fs'); | |
| 23 | + const { kRef, kUnref, FileHandle } = require('internal/fs/promises'); | ||
| 20 | 24 | const { Buffer } = require('buffer'); | |
| 21 | 25 | const { | |
| 22 | 26 | copyObject, | |
@@ -28,6 +32,7 @@ const kIoDone = Symbol('kIoDone'); | |||
| 28 | 32 | const kIsPerformingIO = Symbol('kIsPerformingIO'); | |
| 29 | 33 | ||
| 30 | 34 | const kFs = Symbol('kFs'); | |
| 35 | + const kHandle = Symbol('kHandle'); | ||
| 31 | 36 | ||
| 32 | 37 | function _construct(callback) { | |
| 33 | 38 | const stream = this; | |
@@ -66,6 +71,35 @@ function _construct(callback) { | |||
| 66 | 71 | } | |
| 67 | 72 | } | |
| 68 | 73 | ||
| 74 | + // This generates an fs operations structure for a FileHandle | ||
| 75 | + const FileHandleOperations = (handle) => { | ||
| 76 | + return { | ||
| 77 | + open: (path, flags, mode, cb) => { | ||
| 78 | + throw new ERR_METHOD_NOT_IMPLEMENTED('open()'); | ||
| 79 | + }, | ||
| 80 | + close: (fd, cb) => { | ||
| 81 | + handle[kUnref](); | ||
| 82 | + PromisePrototypeThen(handle.close(), | ||
| 83 | + () => cb(), cb); | ||
| 84 | + }, | ||
| 85 | + read: (fd, buf, offset, length, pos, cb) => { | ||
| 86 | + PromisePrototypeThen(handle.read(buf, offset, length, pos), | ||
| 87 | + (r) => cb(null, r.bytesRead, r.buffer), | ||
| 88 | + (err) => cb(err, 0, buf)); | ||
| 89 | + }, | ||
| 90 | + write: (fd, buf, offset, length, pos, cb) => { | ||
| 91 | + PromisePrototypeThen(handle.write(buf, offset, length, pos), | ||
| 92 | + (r) => cb(null, r.bytesWritten, r.buffer), | ||
| 93 | + (err) => cb(err, 0, buf)); | ||
| 94 | + }, | ||
| 95 | + writev: (fd, buffers, pos, cb) => { | ||
| 96 | + PromisePrototypeThen(handle.writev(buffers, pos), | ||
| 97 | + (r) => cb(null, r.bytesWritten, r.buffers), | ||
| 98 | + (err) => cb(err, 0, buffers)); | ||
| 99 | + } | ||
| 100 | + }; | ||
| 101 | + }; | ||
| 102 | + | ||
| 69 | 103 | function close(stream, err, cb) { | |
| 70 | 104 | if (!stream.fd) { | |
| 71 | 105 | // TODO(ronag) | |
@@ -80,6 +114,32 @@ function close(stream, err, cb) { | |||
| 80 | 114 | } | |
| 81 | 115 | } | |
| 82 | 116 | ||
| 117 | + function importFd(stream, options) { | ||
| 118 | + stream.fd = null; | ||
| 119 | + if (options.fd) { | ||
| 120 | + if (typeof options.fd === 'number') { | ||
| 121 | + // When fd is a raw descriptor, we must keep our fingers crossed | ||
| 122 | + // that the descriptor won't get closed, or worse, replaced with | ||
| 123 | + // another one | ||
| 124 | + // https://github.com/nodejs/node/issues/35862 | ||
| 125 | + stream.fd = options.fd; | ||
| 126 | + } else if (typeof options.fd === 'object' && | ||
| 127 | + options.fd instanceof FileHandle) { | ||
| 128 | + // When fd is a FileHandle we can listen for 'close' events | ||
| 129 | + if (options.fs) | ||
| 130 | + // FileHandle is not supported with custom fs operations | ||
| 131 | + throw new ERR_METHOD_NOT_IMPLEMENTED('FileHandle with fs'); | ||
| 132 | + stream[kHandle] = options.fd; | ||
| 133 | + stream.fd = options.fd.fd; | ||
| 134 | + stream[kFs] = FileHandleOperations(stream[kHandle]); | ||
| 135 | + stream[kHandle][kRef](); | ||
| 136 | + options.fd.on('close', FunctionPrototypeBind(stream.close, stream)); | ||
| 137 | + } else | ||
| 138 | + throw ERR_INVALID_ARG_TYPE('options.fd', | ||
| 139 | + ['number', 'FileHandle'], options.fd); | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + | ||
| 83 | 143 | function ReadStream(path, options) { | |
| 84 | 144 | if (!(this instanceof ReadStream)) | |
| 85 | 145 | return new ReadStream(path, options); | |
@@ -115,10 +175,11 @@ function ReadStream(path, options) { | |||
| 115 | 175 | ||
| 116 | 176 | // Path will be ignored when fd is specified, so it can be falsy | |
| 117 | 177 | this.path = toPathIfFileURL(path); | |
| 118 | - this.fd = options.fd === undefined ? null : options.fd; | ||
| 119 | 178 | this.flags = options.flags === undefined ? 'r' : options.flags; | |
| 120 | 179 | this.mode = options.mode === undefined ? 0o666 : options.mode; | |
| 121 | 180 | ||
| 181 | + importFd(this, options); | ||
| 182 | + | ||
| 122 | 183 | this.start = options.start; | |
| 123 | 184 | this.end = options.end; | |
| 124 | 185 | this.pos = undefined; | |
@@ -287,10 +348,11 @@ function WriteStream(path, options) { | |||
| 287 | 348 | ||
| 288 | 349 | // Path will be ignored when fd is specified, so it can be falsy | |
| 289 | 350 | this.path = toPathIfFileURL(path); | |
| 290 | - this.fd = options.fd === undefined ? null : options.fd; | ||
| 291 | 351 | this.flags = options.flags === undefined ? 'w' : options.flags; | |
| 292 | 352 | this.mode = options.mode === undefined ? 0o666 : options.mode; | |
| 293 | 353 | ||
| 354 | + importFd(this, options); | ||
| 355 | + | ||
| 294 | 356 | this.start = options.start; | |
| 295 | 357 | this.pos = undefined; | |
| 296 | 358 | this.bytesWritten = 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,50 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const fs = require('fs'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const path = require('path'); | ||
| 6 | + const tmpdir = require('../common/tmpdir'); | ||
| 7 | + const file = path.join(tmpdir.path, 'read_stream_filehandle_worker.txt'); | ||
| 8 | + const input = 'hello world'; | ||
| 9 | + const { Worker, isMainThread, workerData } = require('worker_threads'); | ||
| 10 | + | ||
| 11 | + if (isMainThread || !workerData) { | ||
| 12 | + tmpdir.refresh(); | ||
| 13 | + fs.writeFileSync(file, input); | ||
| 14 | + | ||
| 15 | + fs.promises.open(file, 'r').then((handle) => { | ||
| 16 | + handle.on('close', common.mustNotCall()); | ||
| 17 | + new Worker(__filename, { | ||
| 18 | + workerData: { handle }, | ||
| 19 | + transferList: [handle] | ||
| 20 | + }); | ||
| 21 | + }); | ||
| 22 | + fs.promises.open(file, 'r').then((handle) => { | ||
| 23 | + fs.createReadStream(null, { fd: handle }); | ||
| 24 | + assert.throws(() => { | ||
| 25 | + new Worker(__filename, { | ||
| 26 | + workerData: { handle }, | ||
| 27 | + transferList: [handle] | ||
| 28 | + }); | ||
| 29 | + }, { | ||
| 30 | + code: 25, | ||
| 31 | + }); | ||
| 32 | + }); | ||
| 33 | + } else { | ||
| 34 | + let output = ''; | ||
| 35 | + | ||
| 36 | + const handle = workerData.handle; | ||
| 37 | + handle.on('close', common.mustCall()); | ||
| 38 | + const stream = fs.createReadStream(null, { fd: handle }); | ||
| 39 | + | ||
| 40 | + stream.on('data', common.mustCallAtLeast((data) => { | ||
| 41 | + output += data; | ||
| 42 | + })); | ||
| 43 | + | ||
| 44 | + stream.on('end', common.mustCall(() => { | ||
| 45 | + handle.close(); | ||
| 46 | + assert.strictEqual(output, input); | ||
| 47 | + })); | ||
| 48 | + | ||
| 49 | + stream.on('close', common.mustCall()); | ||
| 50 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments