Skip to content

Writable stream is not immediatly closed after .destroy() call #31776

Description

@Veetaha
  • Version: 12.6.0
  • Platform:
  • 64-bit (Windows 10 Education version 1809),
  • Linux lenovo520 4.15.0-70-generic #79-Ubuntu SMP Tue Nov 12 10:36:11 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
  • Subsystem: fs, stream

What steps will reproduce the bug?

Run this code with node

const fs = require("fs");
const { spawnSync } = require("child_process");

const pipeline = require("util").promisify(require("stream").pipeline);

async function main() {
    const src  = fs.createReadStream("src.exe");
    const dest = fs.createWriteStream("dest.exe", { mode: 0o755 });
    await pipeline(src, dest);
    dest.destroy();

    console.dir(spawnSync("dest.exe", ["--version"]));
}

main().finally(() => console.log("DONE"));

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

It doesn't reproduce on all platforms stably. On my laptop it does always reproduce on Windows 10. I am not sure what is the required condition for this...

What is the expected behavior?

As per documentation of .destroy():

This is a destructive and immediate way to destroy a stream

I expect write-stream to release the file handle during the call to .destroy(), so that the next call to spawnSync of the copied file does execute the binary successfully.

What do you see instead?

Windows 10 x64
D:\junk\catch>node index.js
{
  error: Error: spawnSync dest.exe EBUSY
      at Object.spawnSync (internal/child_process.js:1041:20)
      at spawnSync (child_process.js:609:24)
      at main (D:\junk\catch\index.js:12:17)
      at processTicksAndRejections (internal/process/task_queues.js:85:5) {
    errno: 'EBUSY',
    code: 'EBUSY',
    syscall: 'spawnSync dest.exe',
    path: 'dest.exe',
    spawnargs: [ '--version' ]
  },
  status: null,
  signal: null,
  output: null,
  pid: 0,
  stdout: null,
  stderr: null
}
DONE

D:\junk\catch>
Ubuntu 18.04.3
~/my/junk/stream $ node index.js 
{
  error: Error: spawnSync ./dest.exe EACCES
      at Object.spawnSync (internal/child_process.js:1045:20)
      at spawnSync (child_process.js:597:24)
      at main (/home/veetaha/my/junk/stream/index.js:12:17)
      at processTicksAndRejections (internal/process/task_queues.js:97:5) {
    errno: -13,
    code: 'EACCES',
    syscall: 'spawnSync ./dest.exe',
    path: './dest.exe',
    spawnargs: [ '--version' ]
  },
  status: null,
  signal: null,
  output: null,
  pid: 6261,
  stdout: null,
  stderr: null
}
DONE
~/my/junk/stream $ 

Additional information

The original problem was when downloading a binary executable from GitHub releases for rust-analyzer. You can see the initial discussion on the bug here.

The workaround for this that we use now is to wait for "close" event on writable-stream after the call to .destroy(), i.e.

await stream.pipeline(src, dest);
return new Promise(resolve => {
    dest.on("close", resolve);
    dest.destroy();
});

Activity

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

    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