镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Writable is destroyed before calling callback in rare case #40377

Description

@szmarczak

Version

v16.10.0

Platform

Linux solus 5.14.7-198.current #1 SMP PREEMPT Wed Sep 22 16:02:46 UTC 2021 x86_64 GNU/Linux

Subsystem

stream

What steps will reproduce the bug?

const {Writable} = require('stream');

class X extends Writable {
    async _destroy(error, callback) {
        (async () => {
            await new Promise(resolve => setTimeout(resolve, 10));
            console.log(w._writableState.closed);

            callback(error);
        })();
    }
}

const w = new X();

w.once('error', error => {
    console.log(error);
});

w.destroy(new Error('oh no!'));

How often does it reproduce? Is there a required condition?

Always.

What is the expected behavior?

false
Error: oh no!

What do you see instead?

true

Additional information

Some undocumented behavior here:

try {
const result = self._destroy(err || null, onDestroy);
if (result != null) {
const then = result.then;
if (typeof then === 'function') {
then.call(
result,
function() {
process.nextTick(onDestroy, null);
},
function(err) {
process.nextTick(onDestroy, err);
});
}
}
} catch (err) {
onDestroy(err);
}

Activity

  1. changed the title [-]Writable is destroyed before calling callback[/-] [+]Writable is destroyed before calling callback in rare case[/+] on Oct 8, 2021
  2. added
    streamIssues and PRs related to Node.js streams.
    linuxIssues and PRs related to the Linux platform.
    on Oct 8, 2021
  3. targos commented on Oct 9, 2021

    @targos
    Member

    @nodejs/streams

  4. ronag commented on Oct 9, 2021

    @ronag
    Member

    Don’t mix thenable with callback.

  5. mcollina commented on Oct 9, 2021

    @mcollina
    SponsorMember

    The behavior is correct for me. After .destroy() is called, the stream is closed synchronously and no more read or write operation can happen. However the destroy cycle is asynchronous because it might take a while to completely clean up a native resource.

  6. szmarczak commented on Oct 9, 2021

    @szmarczak
    MemberAuthor

    @mcollina I forgot to include Error: oh no! in expected behavior. The error is not emitted. The issue is that if _destroy returns a thenable, there's a race condition - either the callback gets called first or the promise resolves. If it waits for the thenable, it should omit the callback argument (or throw when it gets called). Also the documentation doesn't mention anything about _destroy being thenable. Therefore I consider this a bug.

  7. mcollina commented on Oct 9, 2021

    @mcollina
    SponsorMember

    Uh? Using a thenable there should not be supported.

  8. ronag commented on Oct 9, 2021

    @ronag
    Member

    I think this is a case of insufficient documentation.

  9. mcollina commented on Oct 10, 2021

    @mcollina
    SponsorMember

    I have a feeling I missed something when we added thenable support to destroy.

    I'll need to dig into this and check out what's the problem.

  10. mcollina commented on Oct 10, 2021

    @mcollina
    SponsorMember

    This was added in 744a284 without documentation.

    The error is not printed because it is not rethrown by the _destroy function. Moreover, the callback should not be mixed with async/await (nor is needed).

  11. ronag commented on Oct 10, 2021

    @ronag
    Member

    I think we could maybe emit a warning if function returns a thenable when the function.length has the value for the callback signature.

  12. szmarczak commented on Oct 10, 2021

    @szmarczak
    MemberAuthor

    Wouldn't it be better to throw after the promise resolves and the callback gets called?

  13. mcollina commented on Oct 10, 2021

    @mcollina
    SponsorMember

    Wouldn't it be better to throw after the promise resolves and the callback gets called?

    if you implement _destroy(), you need to rethrow or call the callback with the error passed as an argument.

  14. szmarczak commented on Oct 10, 2021

    @szmarczak
    MemberAuthor

    Again there's this situation where callback may be called after the promise resolves. Currently calling the callback doesn't do anything because at that point the stream is destroyed already because the promise resolved.

    One can expect callback not to throw, so indeed a warning would be sufficient I think.

  15. added
    docIssues and PRs related to Node.js documentation.
    confirmed-bugIssues and PRs for confirmed bugs.
    on Oct 11, 2021
  16. mcollina commented on Oct 11, 2021

    @mcollina
    SponsorMember

    The documentation must be updated to better specify all of this. I would recommend to add a warning as well when using a promise with a callback.

  17. Mesteery commented on Oct 11, 2021

    @Mesteery
    Contributor

    I think this is a good first issue.

  18. RafaelGSS commented on Dec 22, 2021

    @RafaelGSS
    Member

    @mcollina it can be closed #41040 was merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.docIssues and PRs related to Node.js documentation.good first issueIssues that are suitable for first-time contributors.linuxIssues and PRs related to the Linux platform.streamIssues and PRs related to Node.js streams.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions