nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.7k stars 29.64k forks source link

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

Open JAD3N opened 2 months ago

JAD3N commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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

```javascript 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); } })(); ``` ```console ./out/Release/node repros/54453.js && echo "Exit code $?" Exit code 0 ```

Without write call

```javascript 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); } })(); ``` ```console ./out/Release/node repros/54453.js && echo "Exit code $?" Done! Exit code 0 ```

CGQAQ commented 2 months ago

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 commented 2 months ago

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

@nodejs/v8