8000 fs, stream: initial `Symbol.dispose` and `Symbol.asyncDispose` support · nodejs/node@e50c316 · GitHub
[go: up one dir, main page]

Skip to content

Commit e50c316

Browse files
MoLowbenjamingr
authored andcommitted
fs, stream: initial Symbol.dispose and Symbol.asyncDispose support
Co-authored-by: Benjamin Gruenbaum <benjamingr@gmail.com> PR-URL: #48518 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Erick Wendel <erick.workspace@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 0f912a7 commit e50c316

File tree

9 files changed

+93
-0
lines changed
  • test/parallel
  • typings
  • 9 files changed

    +93
    -0
    lines changed

    doc/api/fs.md

    Lines changed: 10 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -817,6 +817,16 @@ On Linux, positional writes don't work when the file is opened in append mode.
    817817
    The kernel ignores the position argument and always appends the data to
    818818
    the end of the file.
    819819
    820+
    #### `filehandle[Symbol.asyncDispose]()`
    821+
    822+
    <!-- YAML
    823+
    added: REPLACEME
    824+
    -->
    825+
    826+
    > Stability: 1 - Experimental
    827+
    828+
    An alias for `filehandle.close()`.
    829+
    820830
    ### `fsPromises.access(path[, mode])`
    821831
    822832
    <!-- YAML

    doc/api/stream.md

    Lines changed: 11 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1904,6 +1904,17 @@ option. In the code example above, data will be in a single chunk if the file
    19041904
    has less then 64 KiB of data because no `highWaterMark` option is provided to
    19051905
    [`fs.createReadStream()`][].
    19061906

    1907+
    ##### `readable[Symbol.asyncDispose]()`
    1908+
    1909+
    <!-- YAML
    1910+
    added: REPLACEME
    1911+
    -->
    1912+
    1913+
    > Stability: 1 - Experimental
    1914+
    1915+
    Calls [`readable.destroy()`][readable-destroy] with an `AbortError` and returns
    1916+
    a promise that fulfills when the stream is finished.
    1917+
    19071918
    ##### `readable.compose(stream[, options])`
    19081919

    19091920
    <!-- YAML

    lib/internal/fs/promises.js

    Lines changed: 5 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -14,6 +14,7 @@ const {
    1414
    SafeArrayIterator,
    1515
    SafePromisePrototypeFinally,
    1616
    Symbol,
    17+
    SymbolAsyncDispose,
    1718
    Uint8Array,
    1819
    FunctionPrototypeBind,
    1920
    } = primordials;
    @@ -246,6 +247,10 @@ class FileHandle extends EventEmitterMixin(JSTransferable) {
    246247
    return this[kClosePromise];
    247248
    };
    248249

    250+
    async [SymbolAsyncDispose]() {
    251+
    return this.close();
    252+
    }
    253+
    249254
    /**
    250255
    * @typedef {import('../webstreams/readablestream').ReadableStream
    251256
    * } ReadableStream

    lib/internal/per_context/primordials.js

    Lines changed: 6 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -228,6 +228,12 @@ function copyPrototype(src, dest, prefix) {
    228228
    copyPrototype(original.prototype, primordials, `${name}Prototype`);
    229229
    });
    230230

    231+
    // Define Symbol.Dispose and Symbol.AsyncDispose
    232+
    // Until these are defined by the environment.
    233+
    // TODO(MoLow): Remove this polyfill once Symbol.dispose and Symbol.asyncDispose are available in V8.
    234+
    primordials.SymbolDispose ??= primordials.SymbolFor('nodejs.dispose');
    235+
    primordials.SymbolAsyncDispose ??= primordials.SymbolFor('nodejs.asyncDispose');
    236+
    231237
    // Create copies of intrinsic objects that require a valid `this` to call
    232238
    // static methods.
    233239
    // Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all

    lib/internal/process/pre_execution.js

    Lines changed: 13 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -8,6 +8,9 @@ const {
    88
    ObjectGetOwnPropertyDescriptor,
    99
    SafeMap,
    1010
    StringPrototypeStartsWith,
    11+
    Symbol,
    12+
    SymbolDispose,
    13+
    SymbolAsyncDispose,
    1114
    globalThis,
    1215
    } = primordials;
    1316

    @@ -82,6 +85,8 @@ function prepareExecution(options) {
    8285

    8386
    require('internal/dns/utils').initializeDns();
    8487

    88+
    setupSymbolDisposePolyfill();
    89+
    8590
    if (isMainThread) {
    8691
    assert(internalBinding('worker').isMainThread);
    8792
    // Worker threads will get the manifest in the message handler.
    @@ -119,6 +124,14 @@ function prepareExecution(options) {
    119124
    }
    120125
    }
    121126

    127+
    function setupSymbolDisposePolyfill() {
    128+
    // TODO(MoLow): Remove this polyfill once Symbol.dispose and Symbol.asyncDispose are available in V8.
    129+
    // eslint-disable-next-line node-core/prefer-primordials
    130+
    Symbol.dispose ??= SymbolDispose;
    131+
    // eslint-disable-next-line node-core/prefer-primordials
    132+
    Symbol.asyncDispose ??= SymbolAsyncDispose;
    133+
    }
    134+
    122135
    function setupUserModules(isLoaderWorker = false) {
    123136
    initializeCJSLoader();
    124137
    initializeESMLoader(isLoaderWorker);

    lib/internal/streams/readable.js

    Lines changed: 11 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -31,6 +31,7 @@ const {
    3131
    ObjectSetPrototypeOf,
    3232
    Promise,
    3333
    SafeSet,
    34+
    SymbolAsyncDispose,
    3435
    SymbolAsyncIterator,
    3536
    Symbol,
    3637
    } = primordials;
    @@ -67,6 +68,7 @@ const {
    6768
    ERR_STREAM_UNSHIFT_AFTER_END_EVENT,
    6869
    ERR_UNKNOWN_ENCODING,
    6970
    },
    71+
    AbortError,
    7072
    } = require('internal/errors');
    7173
    const { validateObject } = require('internal/validators');
    7274

    @@ -234,6 +236,15 @@ Readable.prototype[EE.captureRejectionSymbol] = function(err) {
    234236
    this.destroy(err);
    235237
    };
    236238

    239+
    Readable.prototype[SymbolAsyncDispose] = function() {
    240+
    let error;
    241+
    if (!this.destroyed) {
    242+
    error = this.readableEnded ? null : new AbortError();
    243+
    this.destroy(error);
    244+
    }
    245+
    return new Promise((resolve, reject) => eos(this, (err) => (err && err !== error ? reject(err) : resolve(null))));
    246+
    };
    247+
    237248
    // Manually shove something into the read() buffer.
    238249
    // This returns true if the highWaterMark has not been hit yet,
    239250
    // similar to how Writable.write() returns true if you should
    Lines changed: 12 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,12 @@
    1+
    'use strict';
    2+
    3+
    const common = require('../common');
    4+
    const { promises: fs } = require('fs');
    5+
    6+
    async function doOpen() {
    7+
    const fh = await fs.open(__filename);
    8+
    fh.on('close', common.mustCall());
    9+
    await fh[Symbol.asyncDispose]();
    10+
    }
    11+
    12+
    doOpen().then(common.mustCall());
    Lines changed: 23 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,23 @@
    1+
    'use strict';
    2+
    3+
    const common = require('../common');
    4+
    const { Readable } = require('stream');
    5+
    const assert = require('assert');
    6+
    7+
    {
    8+
    const read = new Readable({
    9+
    read() {}
    10+
    });
    11+
    read.resume();
    12+
    13+
    read.on('end', common.mustNotCall('no end event'));
    14+
    read.on('close', common.mustCall());
    15+
    read.on('error', common.mustCall((err) => {
    16+
    assert.strictEqual(err.name, 'AbortError');
    17+
    }));
    18+
    19+
    read[Symbol.asyncDispose]().then(common.mustCall(() => {
    20+
    assert.strictEqual(read.errored.name, 'AbortError');
    21+
    assert.strictEqual(read.destroyed, true);
    22+
    }));
    23+
    }

    typings/primordials.d.ts

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -434,6 +434,8 @@ declare namespace primordials {
    434434
    export const SymbolFor: typeof Symbol.for
    435435
    export const SymbolKeyFor: typeof Symbol.keyFor
    436436
    export const SymbolAsyncIterator: typeof Symbol.asyncIterator
    437+
    export const SymbolDispose: typeof Symbol // TODO(MoLow): use typeof Symbol.dispose when it's available
    438+
    export const SymbolAsyncDispose: typeof Symbol // TODO(MoLow): use typeof Symbol.asyncDispose when it's available
    437439
    export const SymbolHasInstance: typeof Symbol.hasInstance
    438440
    export const SymbolIsConcatSpreadable: typeof Symbol.isConcatSpreadable
    439441
    export const SymbolIterator: typeof Symbol.iterator

    0 commit comments

    Comments
     (0)
    0