Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closing WritableStream from TransformStream causes Node.js process to crash #54453

Open
JAD3N opened this issue Aug 19, 2024 · 6 comments
Open

Comments

@JAD3N
Copy link

JAD3N commented Aug 19, 2024

Version

v20.16.0

Platform

Linux laptop 6.1.0-23-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.99-1 (2024-07-15) x86_64 GNU/Linux

Subsystem

No response

What steps will reproduce the bug?

I ran this code and my Node.js process crashed with no output:

This causes a crash:

import { TransformStream } from "node:stream/web";

const { writable } = new TransformStream();

try {
  let writer = writable.getWriter();
  await writer.write(" ");
  writer.releaseLock();

  writer = writable.getWriter();
  await writer.close();
  writer.releaseLock();

  console.log("Done!");
} catch (err) {
  console.error("Error:", err);
}

This doesn't:

const stream = new WritableStream();

try {
  let writer = stream.getWriter();
  await writer.write(" ");
  writer.releaseLock();

  writer = stream.getWriter();
  await writer.close();
  writer.releaseLock();

  console.log("Done!");
} catch (err) {
  console.error("Error:", err);
}

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

If I close the WritableStream on the TransformStream example without awaiting the close() promise then the process will proceed to log Done!.

What is the expected behavior? Why is that the expected behavior?

I figured the stream would close or throw some kind of error if it cannot.

What do you see instead?

The Node.js process crashes without any thrown errors and doesn't trigger the beforeExit or exit process events.

Additional information

I've simplified my code into the reproducible above, that's why I create multiple writers.

No response

@RedYetiDev
Copy link
Member

RedYetiDev commented Aug 19, 2024

Your original code threw an error for me:

Warning: Detected unsettled top-level await at file:///index.mjs:7

But with some slight modifications, I was able to reproduce:

import { TransformStream } from "node:stream/web";

const { writable } = new TransformStream();

(async () => {
  try {
    let writer = writable.getWriter();
    await writer.write(" ");
    writer.releaseLock();
  
    writer = writable.getWriter();
    await writer.close();
    writer.releaseLock();
  
    console.log("Done!");
  } catch (err) {
    console.error("Error:", err);
  }
})();
$ node repro.async.mjs && echo "Exit code: $?"
Exit code: 0

Compared to other streams:

const stream = new WritableStream();

try {
  let writer = stream.getWriter();
  await writer.write(" ");
  writer.releaseLock();

  writer = stream.getWriter();
  await writer.close();
  writer.releaseLock();

  console.log("Done!");
} catch (err) {
  console.error("Error:", err);
}
$ node repro.async.mjs && echo "Exit code: $?"
Done!
Exit code: 0

@RedYetiDev
Copy link
Member

RedYetiDev commented Aug 19, 2024

IIUC The issue has to do with await writer.write(" ");:

import { TransformStream } from "node:stream/web";
await (new TransformStream()).writable.getWriter().write(" ");
Warning: Detected unsettled top-level await at file:///repro.mjs:2

@JAD3N
Copy link
Author

JAD3N commented Aug 19, 2024

Your original code threw an error for me:

Warning: Detected unsettled top-level await at file:///index.mjs:7

But with some slight modifications, I was able to reproduce:

import { TransformStream } from "node:stream/web";

const { writable } = new TransformStream();

(async () => {
  try {
    let writer = writable.getWriter();
    await writer.write(" ");
    writer.releaseLock();
  
    writer = writable.getWriter();
    await writer.close();
    writer.releaseLock();
  
    console.log("Done!");
  } catch (err) {
    console.error("Error:", err);
  }
})();
$ node repro.async.mjs && echo "Exit code: $?"
Exit code: 0

Compared to other streams:

const stream = new WritableStream();

try {
  let writer = stream.getWriter();
  await writer.write(" ");
  writer.releaseLock();

  writer = stream.getWriter();
  await writer.close();
  writer.releaseLock();

  console.log("Done!");
} catch (err) {
  console.error("Error:", err);
}
$ node repro.async.mjs && echo "Exit code: $?"
Done!
Exit code: 0

With your example I also experience a crash and no output in my terminal.

Edit: The exit code also shows as 0

Screenshot_20240819_163933

@CGQAQ
Copy link
Contributor

CGQAQ commented Aug 20, 2024

IIUC The issue has to do with await writer.write(" ");:

import { TransformStream } from "node:stream/web";
await (new TransformStream()).writable.getWriter().write(" ");
Warning: Detected unsettled top-level await at file:///repro.mjs:2

Yes, it's the write blocking entire thread, not the close line

With write call

import { TransformStream } from "node:stream/web";

const { writable } = new TransformStream();

(async () => {
  try {
    let writer = writable.getWriter();
    await writer.write(" ");
  
    console.log("Done!");
  } catch (err) {
    console.error("Error:", err);
  }
})();
./out/Release/node repros/54453.js && echo "Exit code $?"
Exit code 0

Without write call

import { TransformStream } from "node:stream/web";

const { writable } = new TransformStream();

(async () => {
  try {
    let writer = writable.getWriter();
    // await writer.write(" ");
  
    console.log("Done!");
  } catch (err) {
    console.error("Error:", err);
  }
})();
./out/Release/node repros/54453.js && echo "Exit code $?"
Done!
Exit code 0

@CGQAQ
Copy link
Contributor

CGQAQ commented Aug 20, 2024

I see what's going on here:

you need to read to resolve write.
e.g.:

import {
    TransformStream,
  } from 'node:stream/web';
  
const transform = new TransformStream();

await Promise.all([
    transform.writable.getWriter().write('A'),
    transform.readable.getReader().read(),
]); 

console.log("Done!");
./out/Release/node repros/54453.js && echo "Exit code $?"
Done!
Exit code 0

Without read:

import {
    TransformStream,
  } from 'node:stream/web';
  
const transform = new TransformStream();

await Promise.all([
    transform.writable.getWriter().write('A'),
    // transform.readable.getReader().read(),
]); 

console.log("Done!");
./out/Release/node repros/54453.js && echo "Exit code $?"
Exit code 0

edit:
Just tested it on chrome 127.0.6533.120, the behavior is the same

@RedYetiDev
Copy link
Member

Just tested it on chrome 127.0.6533.120, the behavior is the same

@nodejs/v8

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

No branches or pull requests

4 participants