Skip to content

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

Closed
@Veetaha

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();
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions